133 Commits

Author SHA1 Message Date
huangping 7fc1fa3727 feat: add demo mode for prototype deployment (no backend) 2026-06-09 09:45:10 +08:00
huangping 4c2db92da4 fix: set vite base path for /authorization-sdk/ deployment 2026-06-09 09:43:21 +08:00
huangping 6a31b479d5 chore: update native Cargo.lock
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:38:20 +08:00
huangping c2a285c781 fix: handle project count edge case in customer summary
Minor fix to ProjectService for correct customer-project counting.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:24 +08:00
huangping 1333cb38d6 docs: add AGENTS.md, code audit reports, and implementation plans
Added hierarchical AGENTS.md files for root, java, native, services, web modules. Added comprehensive audit reports covering PRD progress, UI audit, full version gap analysis, code audit findings, and ONLYOFFICE status.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:24 +08:00
huangping b2968dc327 docs: update PRD implementation status and fix application config
Updated all module status columns in product modules doc to reflect actual code state. Removed duplicate @Bean import in SecurityConfig.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:16 +08:00
huangping 1492e91431 feat: add notification send service for M8-F03
NotificationSendService dispatches events through configured channels (in-app todo, email placeholder, WeChat placeholder). Supports event type routing and role-based delivery.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:16 +08:00
huangping 2609ea3f79 fix: update stale labels and add callback backlog stats card
Fixed login page (removed I1 tag, updated demo accounts). Added backlog stats bar to CallbackInboxView. Fixed size:500 to size:200 across all list views to match backend @Max(200) validation.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:16 +08:00
huangping 2e4caf72ce feat: add sidebar grouping, auth store persistence fix, idle timeout
Sidebar now groups menu items into Business/Operations/Analytics/System sections. Auth store restores roles/permissions from JWT on page reload. Added idleTimer utility for session timeout.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:09 +08:00
huangping 8ee9aa51d8 feat: add ONLYOFFICE document preview for contract attachments
DocumentPreviewController provides preview config and file streaming endpoints. ContractDetailView adds 'preview' button to attachment list and opens ONLYOFFICE iframe dialog.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:09 +08:00
huangping 8c788ea388 feat: add dashboard with ECharts and SN/callback statistics
Added sn-stats and callback-stats endpoints. HomeView now shows stat cards, pending todos, recent activity, and ECharts pie charts for SN status distribution and callback status. Installed echarts dependency.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:09 +08:00
huangping 5d50d2819b feat: add system params persistence and delivery gate enforcement
V25 migration creates platform_system_param table. SystemParamController replaces localStorage MVP with backend persistence. LicenseSnService.create now checks deliveryGateEnabled flag and blocks SN creation when gate is on but no deliveries completed.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:02 +08:00
huangping 8c167d4909 feat: add user management CRUD and platform_user table
V24 migration creates platform_user table. Backend UserAdminController provides list/create/update/toggleStatus. Frontend UserManagementView enables admin to add/edit/disable users. Replaces hardcoded auth with database-backed user lifecycle.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:02 +08:00
huangping 118790486a fix: rewrite AuthController with database-driven authentication
Replaced hardcoded admin/sales/delivery/ops users with PlatformUser table lookups. Fixed changePassword to use JWT SecurityContext for current user lookup. Implemented real resetPassword and forceLogout endpoints (previously no-ops). Added BCrypt password verification.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:37:02 +08:00
huangping 7fb3eb53c3 feat: add customer summary aggregation with real contract count
Fixed getCustomerSummary() to actually query contract count instead of returning hardcoded 0. Injected PlatformContractMapper for the query.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:36:53 +08:00
huangping 23984a3651 feat: add failure reason persistence and batch replay endpoints to callback inbox
Added failureReason field to CallbackInboxStatusPatchRequest so Ops can categorize failure causes. Added POST /batch-replay for mass reprocess and GET /stats/backlog for backlog monitoring.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:36:53 +08:00
huangping 25395a648b fix: remove error message leakage in LicenseController and ContractController
Replaced try-catch blocks returning e.getMessage() in HTTP 500 responses with proper ResponseStatusException propagation through global ApiExceptionHandler. Added file size (50MB) and MIME type whitelist validation to contract attachment upload.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:36:53 +08:00
huangping 0abb60fd2d fix: resolve 500 errors from missing @RequestParam value attributes
All @RequestParam annotations without explicit value= attributes cause parameter name resolution failures when Java -parameters compiler flag is not set. Fixed AuditController, IntegrationCatalogController, CustomerController, ReportController, UserAdminController, SecurityConfig.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-27 08:36:43 +08:00
huangping 4913d1c556 feat(cli): add migrate command, platform API, config management 2026-05-25 15:20:13 +08:00
huangping 4b79533c70 feat(cli): add craftlabs-auth-cli with status/activate/check/info/release commands 2026-05-25 15:16:39 +08:00
huangping 027ecbd375 feat(sdk): add JNA bridge to replace JNI for Rust C ABI access 2026-05-25 15:15:14 +08:00
huangping f82a2a7b24 docs: add CLI tool design and update implementation order (SDK->CLI->GUI) 2026-05-25 15:12:08 +08:00
huangping 563844e361 docs: add client authorization tool design spec (Tauri + Vue 3) 2026-05-25 15:10:00 +08:00
huangping 147142f44f feat(m10): add audit retention policy configuration 2026-05-25 15:05:54 +08:00
huangping 250c5cbfeb feat(m9): add subscription report config (localStorage MVP) 2026-05-25 15:05:18 +08:00
huangping 1cef437fb3 feat(m5): add simulated callback event delivery for testing 2026-05-25 15:04:03 +08:00
huangping ca1279162b feat(m1): add customer freeze/unfreeze 2026-05-25 15:03:23 +08:00
huangping d3d26ba9b4 feat(sdk): add JNI bridge between Java NativeBridge and Rust C ABI 2026-05-25 15:02:12 +08:00
huangping 7104976bf9 feat(m11): add password reset, owner fields, system params 2026-05-25 14:53:02 +08:00
huangping d6750f1e93 feat: add feature mapping view and notification template config 2026-05-25 14:52:45 +08:00
huangping 0ae3987fb2 feat(m5): add failure reason tagging and batch retry 2026-05-25 14:52:06 +08:00
huangping 6522f02b54 feat(m4): add batch SN ops, delivery gate, console link, SN tags 2026-05-25 14:51:41 +08:00
huangping 4dc8341e7e feat(m2): add SKU mapping between contract lines and license features 2026-05-25 14:50:31 +08:00
huangping 8d1081b2b9 feat(m2): add external/internal order ID to contracts 2026-05-25 14:49:40 +08:00
huangping 16ab474bee feat(m3): add field environment info (address, network, contact) to delivery 2026-05-25 14:49:34 +08:00
huangping 0062b20ea1 feat(m11): expand v-permission to all CRUD pages 2026-05-25 14:46:42 +08:00
huangping 85d2b85b6a fix: add userId filter to audit search and login lockout logic 2026-05-25 14:46:42 +08:00
huangping e96383433d feat(m1): add project stakeholder CRUD 2026-05-25 14:46:30 +08:00
huangping 4bbf1f552f feat(m2): add signing/effective/end date fields to contracts 2026-05-25 14:46:19 +08:00
huangping 1726f486fa fix: add amount UI to contract lines and reason code to SN activation 2026-05-25 14:45:54 +08:00
huangping 13c42d2c87 docs: add remaining gaps WBS execution plan (P0/P1/P2 + SDK JNI) 2026-05-25 11:13:56 +08:00
huangping ff534fc325 ci: add Gitea Actions deploy workflow and runner setup guide 2026-05-25 11:08:48 +08:00
huangping 769bf721f4 fix: handle ConstraintViolationException as 400 instead of 500 2026-05-25 02:38:29 +08:00
huangping 14b86df124 docs(db): add Chinese comments for all 28 platform tables and columns 2026-05-25 02:28:02 +08:00
huangping 3bb19537fe feat(m11): add v-permission directive and button-level permission codes 2026-05-25 01:41:50 +08:00
huangping 1f599e5646 feat(m11): align role model with product definition (SALES/DELIVERY/LICENSE_OPS) 2026-05-25 01:41:04 +08:00
huangping d933639518 feat(m10): add audit event search and CSV export 2026-05-25 01:38:48 +08:00
huangping c088c0ed71 feat(m9): add CSV export for contract-sn report 2026-05-25 01:37:37 +08:00
huangping 46f28d2d97 feat(m6): add JSON template CRUD with versioning 2026-05-25 01:36:40 +08:00
huangping ae880c47b2 feat(m6): add BitAnswer ID mapping CRUD 2026-05-25 01:35:25 +08:00
huangping 36b6e395c5 feat(m11): add password change with profile page 2026-05-25 01:33:54 +08:00
huangping 3ab1165e69 feat(m11): add login failure lockout after 5 failed attempts in 15 min 2026-05-25 01:33:11 +08:00
huangping c2118b16aa feat(m11): add idle timeout auto-logout with warning dialog 2026-05-25 01:32:51 +08:00
huangping 33773928c3 feat(m2): add contract change versioning with CHANGING state 2026-05-25 01:32:20 +08:00
huangping 88c4e22d36 feat(m2): add contract attachment upload and listing 2026-05-25 01:31:01 +08:00
huangping cc7fef8ae9 feat(m4): add SN batch import with text area dialog 2026-05-25 01:29:40 +08:00
huangping b5317d8f58 feat(m1): add customer detail aggregation view 2026-05-25 01:28:36 +08:00
huangping bfb8f23399 feat(m1): add planned start/end dates and project manager to projects 2026-05-25 01:27:15 +08:00
huangping b536a999f0 feat(m1): add industry/address/billing/customerCode fields to customer 2026-05-25 01:26:22 +08:00
huangping 9be9fc4b47 docs: add WBS execution plan for remaining P0/P1/P2 work 2026-05-25 01:21:14 +08:00
huangping d0783aa893 feat(m6): add CRUD dialogs for environments and product lines 2026-05-25 01:15:56 +08:00
huangping ea233dd039 fix(report): implement getProjectHealth with real data queries 2026-05-25 01:15:01 +08:00
huangping 4a9468fcdd fix(home): add M7/M8/M9 quick links to home page 2026-05-25 01:14:35 +08:00
huangping 8ba73c028c feat(web): add M7/M8/M9 sidebar menu entries 2026-05-25 01:08:17 +08:00
huangping 822774b711 feat(web): add M9 reporting pages (contract-sn, callback stats, project health) 2026-05-25 01:07:44 +08:00
huangping 830ea626c9 feat(web): add M8 todo center and notification settings pages 2026-05-25 01:06:28 +08:00
huangping 54e0f8a054 feat(web): add M7 device list and detail pages
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-25 01:05:38 +08:00
huangping a5d250214c feat(web): add M7/M8/M9 API client methods 2026-05-25 01:04:30 +08:00
huangping 8b00f401be feat: add M9 ReportService + ReportController 2026-05-25 01:04:04 +08:00
huangping ba38897f73 feat: add M8 TodoService + TodoController 2026-05-25 01:02:46 +08:00
huangping fa2a50e755 feat(db): add M8 notification and todo tables 2026-05-25 01:01:34 +08:00
huangping 75e6d6d5ec feat: add M7 DeviceController + Callback device event linking 2026-05-25 01:00:43 +08:00
huangping f94f2b91e8 feat: add M7 DeviceService and DTOs 2026-05-25 00:59:12 +08:00
huangping 58b947a366 feat(m7): add DeviceStatus enum and PlatformDevice/PlatformDeviceSnBinding/PlatformDeviceSwapRequest entities with mappers 2026-05-25 00:57:10 +08:00
huangping b13d17702e feat(db): add M7 device management tables (device, sn_binding, swap_request) 2026-05-25 00:54:42 +08:00
huangping 0a43f8fbbe docs: add Mid prototype design spec (M7 device / M8 notification / M9 reporting) 2026-05-25 00:50:38 +08:00
huangping 339695c851 docs: add frontend framework design (10 sections: architecture, modules, layout, components, templates, dataflow, conventions) 2026-05-19 10:35:49 +08:00
huangping f0366ccc47 feat(web): add visual design system page (colors, typography, layout, components, dialog, pages) 2026-05-19 10:14:56 +08:00
huangping d3a646c325 docs: add comprehensive design system (tokens, layout, components, dialog, implementation guide) 2026-05-19 10:11:17 +08:00
huangping 3dc71de739 feat(web): add standalone HTML design demo with full layout + 3 dialog types + color tokens 2026-05-19 08:25:44 +08:00
huangping ffbb0652d9 feat(web): add LicenseList page with el-tree panel (280px), stats, filter, table, issue dialog 2026-05-19 07:25:17 +08:00
huangping 619d75453d feat(web): apply Figma design tokens to MainLayout (white sidebar, 60px header, breadcrumb, global search, theme variables) 2026-05-19 07:20:45 +08:00
huangping eefcc06a5e docs: add design specification and 6-tab comparison audit (49 items, color/layout/dialog/component/type/spec) 2026-05-19 06:29:37 +08:00
huangping 51c2598fb7 feat(web): add full dialog layout simulation (issue/create/detail/confirm/revoke) with Figma tokens 2026-05-18 23:55:57 +08:00
huangping 9f3da47574 feat(web): add full platform layout mockup with Figma tokens - all business modules interactive 2026-05-18 23:44:27 +08:00
huangping 80a6e2716b feat(web): add Figma layout structure comparison audit page (11 gaps analyzed) 2026-05-18 23:34:36 +08:00
huangping 34e15dd650 feat(web): add 3-way theme comparison (Figma / ElementPlus / Hybrid-recommended) 2026-05-18 23:21:18 +08:00
huangping 9cb2fda66a feat(web): add Figma vs Element Plus license management theme comparison page 2026-05-18 23:14:39 +08:00
huangping be772db94b chore: update Cargo.lock and crypto test import 2026-05-18 22:41:46 +08:00
huangping 5d615dd393 feat: add build.rs pubkey embedding and webhook license/v1 endpoints 2026-05-18 22:39:05 +08:00
huangping ebb3da2ad6 feat(rust): add online activation and heartbeat (HTTPS + HMAC signing) for selfhosted provider 2026-05-18 22:37:03 +08:00
huangping 6f79bb97d9 feat(platform): add LicenseSigner, LicenseService, LicenseController, and persistence entities 2026-05-18 22:27:03 +08:00
huangping 91aabb500c feat: extend Java config, Schema, and DB for selfhosted licensing SDK 2026-05-18 22:20:14 +08:00
huangping fbce298f2b feat(rust): add Provider trait + refactor C ABI to route through Provider
SelfHostedProvider implements Provider trait for offline license verification
2026-05-18 22:17:05 +08:00
huangping 8b90a71077 feat(rust): add device fingerprint and selfhosted provider (cache, license verify, offline validation) 2026-05-18 22:12:49 +08:00
huangping f9203e077e feat(rust): add crypto module (HKDF + AES-256-GCM + RSA verify) 2026-05-18 22:05:36 +08:00
huangping b7a947409a build(native): rename lib to craftlabs_auth_core, add crypto deps
feat(rust): extend error codes for selfhosted licensing (crypto/license state variants)
2026-05-18 22:02:28 +08:00
huangping 9bb5cbba64 docs: complete implementation plan for selfhosted licensing SDK (25 tasks, 4 phases) 2026-05-18 21:21:15 +08:00
huangping d7469afee9 docs: self-hosted licensing SDK design spec 2026-05-18 21:00:56 +08:00
huangping dc74c19be4 feat(native): add session management with global handle registry 2026-05-01 14:05:16 +08:00
huangping d716719428 feat(native): add LicenseError enum with BitAnswer error code mapping 2026-05-01 14:02:58 +08:00
huangping 307a019d48 plan: implement BitAnswer 1:1 refactor — 12 tasks across 4 phases
Phase 1 (infrastructure, no breaking changes):
- Rust: error.rs, session.rs, ffi/bitanswer.rs, ffi/bridge.rs
- Rust: refactor lib.rs to session-based handle management
- Java: @Deprecate AuthProvider, create 6 capability interfaces
- Java: LicenseSession skeleton, NativeBridge 30+ method stubs
- Java: CraftLicense new entry point

Phase 2-4: core API expansion, advanced features, cleanup
2026-05-01 13:57:46 +08:00
huangping 7af83b089e docs: add BitAnswer 1:1 mapping refactor design spec
Architecture audit revealed AuthProvider (8 methods) only covers
~15% of BitAnswer's 50+ C API surface. This spec proposes:
- 6 capability interfaces (LicenseLifecycle, FeatureManagement,
  DataItemStore, LicenseInfoQuery, CheckoutManager, LicenseUtility)
- 40+ JNI → Rust → BitAnswer C API mappings
- 4-phase migration path with backward compatibility
2026-05-01 13:45:43 +08:00
huangping 313315cd3f chore: archive old C++ CMake build to .deprecated-cmake/; Rust is now canonical 2026-04-28 22:47:45 +08:00
huangping b7f756bc2b feat(rust): complete M5 security hardening — dynamic API, obfuscation, libloading 2026-04-28 22:45:49 +08:00
huangping 6a92f46447 feat(rust): split core library into activate/license/heartbeat modules with build.rs and C ABI tests 2026-04-28 18:46:20 +08:00
huangping 6b3f1bdab5 chore: upgrade Spring Boot 3.4.5 -> 4.0.0 with compiler and SecurityConfig fixes
- Move maven-compiler-plugin from pluginManagement to plugins for proper release 17 inheritance
- Remove deprecated UserDetailsServiceAutoConfiguration exclude (removed in Boot 4.0)
- Switch MyBatis-Plus to boot4-starter
2026-04-28 18:39:54 +08:00
huangping 30cd1ec51a chore: update .gitignore for .env and README with docker build instructions 2026-04-28 18:32:08 +08:00
huangping d884e6bab2 feat: expand docker-compose to 8 services with monitoring stack per architecture spec 2026-04-28 18:31:49 +08:00
huangping 53c52a0b3e refactor(native): rename C API auth_* -> craft_* across header, core, JNI bridge, and smoke tests 2026-04-28 18:27:34 +08:00
huangping 5073a4193f refactor(native): rename C API auth_* -> craft_* in JNI bridge 2026-04-28 18:23:51 +08:00
huangping 650c1caffa docs(engineering): I8/I9 iteration artifacts and frontend UI specification
Add I8/I9 design and implementation review markdown, update parallel iteration
index and I7 review cross-links, and add FRONTEND_UI_SPECIFICATION for design
handoff (Figma Make).

Made-with: Cursor
2026-04-07 21:26:51 +08:00
huangping d53ddf32c8 feat(i8-i9): webhook DEAD replay, read-only delivery status, and callback UI
I8: platform proxies replay to webhook; webhook ops token filter and internal
replay endpoint; delivery service supports read/replay flows.

I9: platform GET callback webhook delivery status by inbox id; UI shows
read-only status block and handles load errors without blocking the page.

Also refresh OpenAPI, Runbook notes, test fixtures and YAML; fix Vite dev
axios baseURL so /api uses proxy; improve login error messaging.

Made-with: Cursor
2026-04-07 21:26:44 +08:00
huangping 5e051633ec docs: sync I5_I6 design with I7 (OPS, async delivery); index I7 row
Made-with: Cursor
2026-04-06 23:06:07 +08:00
huangping 5fe7181b35 feat(i7): async webhook delivery queue, OPS RBAC, UI role routing; docs and runbook
- Architect: I7_DESIGN.md, I7_IMPLEMENTATION_REVIEW.md; parallel index + track B
- Backend: @EnableMethodSecurity; OPS login; CallbackInbox PreAuthorize; IntegrationCatalog triple role
- Webhook: V2 webhook_platform_delivery; planner + scheduler + single-shot forwarder; tests
- Frontend: Pinia hasAnyRole; MainLayout/HomeView/router for OPS vs dev
- Runbook §10.5 delivery config

Made-with: Cursor
2026-04-06 23:01:10 +08:00
huangping ce49fe143c docs(i6): solidify I5_I6 design markdown; add Dependabot and ci-security (Trivy, npm audit)
Made-with: Cursor
2026-04-06 22:48:58 +08:00
huangping 499fef3c2f feat(web): VITE_API_BASE and I6 home module navigation
Made-with: Cursor
2026-04-06 22:46:31 +08:00
huangping d9536802db feat(platform): add I6 security headers for API chains
Made-with: Cursor
2026-04-06 22:46:31 +08:00
huangping 78433faa89 docs(i6): UAT closeout, architecture review, Runbook internal token
Made-with: Cursor
2026-04-06 22:46:31 +08:00
huangping 841bd3e0bd feat(web): I5 callback inbox and integration catalog UI
Made-with: Cursor
2026-04-06 22:40:28 +08:00
huangping e34b420168 feat(webhook): forward BitAnswer callbacks to platform after first receipt
Made-with: Cursor
2026-04-06 22:40:26 +08:00
huangping fc0c4b1930 feat(platform): I5 callback inbox, internal ingest, and M6 catalog APIs
Made-with: Cursor
2026-04-06 22:40:21 +08:00
huangping b6e110acaf docs(i5i6): add I5/I6 design and fix SDK doc backticks
Made-with: Cursor
2026-04-06 22:40:16 +08:00
huangping 00411a5e74 feat(web): I4 delivery and license SN UI
Add routes, menu entries, platform API helpers, and views for delivery
batches and license SN management.

Made-with: Cursor
2026-04-06 21:49:10 +08:00
huangping 9df6f60a17 feat(platform): I4 delivery batches, lines, and license SN APIs
Add Flyway V4 tables, delivery-batches and license-sns endpoints with
validation, audit actions, controller tests, and OpenAPI snapshot update.

Made-with: Cursor
2026-04-06 21:49:04 +08:00
huangping df91ab0673 docs(i4): add I4 design for M3 delivery and M4 license SN
Describe REST contracts, validation, routing, and I4 sync checklist
aligned with V4 schema and parallel iteration index.

Made-with: Cursor
2026-04-06 21:48:55 +08:00
huangping 7f8e7b7e7c feat(web): I3 contract list, wizard, and detail
Add routes and menu, platform API helpers (patch status, audit-events),
and Vue views aligned to platform contract DTOs and state transitions.

Made-with: Cursor
2026-04-06 21:29:28 +08:00
huangping 69f7ee11df feat(platform): I3 contracts, lines, status machine, and audit API
Add Flyway V3 tables, contract CRUD and line endpoints, PATCH status
transitions with validation, M10-F01 audit-events listing, 409 handler,
and integration tests. Refresh OpenAPI contract snapshot.

Made-with: Cursor
2026-04-06 21:29:21 +08:00
huangping 5b50bf0fd8 docs(i3): add I3 design for M2 contracts and M10-F01 audit
Document REST shape, state machine, audit query, and Webhook DTO v0.1
alignment for iteration I3 (parallel tracks + product M2 P0).

Made-with: Cursor
2026-04-06 21:29:17 +08:00
huangping f94f03bcc2 feat(sdk): AuthConfigs, JSON Schema, examples, and release checksum CI
Add craftlabs-auth-config.schema.json, Java AuthConfigs model with tests,
example configs aligned to BP-10, C/Java/auth-config documentation,
native header notes, RELEASING guide, and workflow to verify SDK
artifact checksums on release tags.

Made-with: Cursor
2026-04-06 21:05:12 +08:00
huangping 65eb983035 feat(web): I1 shell and I2 customer/project UI
Vue 3 + Element Plus layout with JWT login, RBAC routes, axios 401
handling with token restore, and Customers/Projects views wired to
platform APIs.

Made-with: Cursor
2026-04-06 21:05:02 +08:00
huangping 3f577b34d5 feat(platform): I1 bootstrap, I2 M1 APIs, OpenAPI SSOT, and CI guards
Deliver dual Spring Boot services (platform API + webhook ingress), JWT
auth, Flyway with isolated history tables, customer/project/dictionary
endpoints, OpenAPI snapshot under contracts/, RUNBOOK, and CI that runs
on services/web/contracts paths plus enforcer + dependency tree ban on
craftlabs-auth-bitanswer.

Made-with: Cursor
2026-04-06 21:04:56 +08:00
huangping 76ff98db87 docs(i1): engineering index, parallel tracks, and product context
Add PARALLEL_ITERATION_INDEX, workspace layout, system architecture,
three-track execution packs, BPM/product references, and planned
service manifests. Supports I1 alignment across backend, web, and SDK.

Made-with: Cursor
2026-04-06 21:04:49 +08:00
439 changed files with 52083 additions and 68 deletions
+20
View File
@@ -0,0 +1,20 @@
# CraftLabs 平台环境变量模板
# 复制为 .env 后修改实际值;.env 不提交 Git
# 数据库
DB_PASSWORD=change_me_in_production
# Redis 密码(空字符串表示无密码)
REDIS_PASSWORD=
# 比特安索 API Key
BIT_CLOUD_API_KEY=change_me
# Webhook 预期 Token(与比特控制台配置一致)
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN=change_me
# JWT 签名密钥(≥32 字符)
PLATFORM_JWT_SECRET=change_me_at_least_32_characters_long
# Grafana 管理员密码
GRAFANA_ADMIN_PASSWORD=admin
+7
View File
@@ -0,0 +1,7 @@
{
"name": "安徽地质博物馆v2.0",
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
"nodeId": "0-38499",
"url": "https://www.figma.com/design/TdU1qb5xVYDLOssDOQxQqv/",
"fetchedAt": "2026-05-18T00:00:00Z"
}
+56
View File
@@ -0,0 +1,56 @@
{
"file": {
"name": "安徽地质博物馆v2.0",
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
"lastModified": "2026-05-18T14:45:48Z",
"frame": "数字资源系统-资源管理",
"frameSize": "1920x1080"
},
"colors": {
"pageBackground": "rgba(234,239,250,1.0)",
"cardBackground": "rgba(255,255,255,1.0)",
"textPrimary": "rgba(0,0,0,1.0)",
"textSecondary": "rgba(49,49,49,1.0)",
"textOnPrimary": "rgba(255,255,255,1.0)",
"badgeRed": "rgba(213,73,65,1.0)",
"decorativeBlue": "rgba(207,209,255,1.0)",
"decorativeTeal": "rgba(217,248,255,1.0)"
},
"typography": {
"body": { "fontSize": "14px", "color": "rgba(0,0,0,1.0)" },
"badge": { "fontSize": "12px", "color": "rgba(255,255,255,1.0)" },
"placeholder": { "fontSize": "14px", "color": "rgba(49,49,49,1.0)" }
},
"layout": {
"frameWidth": 1920,
"frameHeight": 1080,
"headerHeight": 60,
"sidebarWidth": 232,
"contentWidth": 1688,
"contentPaddingX": 20,
"treePanelWidth": 280,
"mainPanelWidth": 1368,
"breadcrumbHeight": 46
},
"components": {
"header": "headerMenu 顶部菜单导航",
"sidebar": "Menu - 侧边菜单",
"breadcrumb": "Breadcrumb 面包屑 (数字资源 > 资源管理)",
"tree": "Tree 树结构 - 资源分类树",
"search": "search 搜索框",
"menuItems": [
"item/menuLogo/baseLogo-light",
"item/normalMenu/1st-light (x11 菜单项)",
"Button"
],
"headerItems": [
"icon-search-w/text - 资源快速搜索",
"icon-internet",
"icon-view-module",
"icon-mail + Badge (红点通知 2)",
"logo-github",
"icon-user w/ TD Admin",
"icon-setting"
]
}
}
File diff suppressed because one or more lines are too long
+104
View File
@@ -0,0 +1,104 @@
# Gitea Actions: 平台部署流水线
# 触发条件:推送 main 分支 或 手动触发
# 运行环境:self-hosted runner(需要安装 docker + docker-compose
name: deploy
on:
push:
branches: [main]
paths:
- "services/**"
- "web/**"
- "services/docker-compose.yml"
workflow_dispatch:
env:
REGISTRY: gitea.craftlabs.cn/craftlabs
API_IMAGE: delivery-platform-api
WEBHOOK_IMAGE: license-webhook-ingress
UI_IMAGE: delivery-platform-ui
jobs:
build-and-deploy:
runs-on: ubuntu-latest # self-hosted runner 需注册该标签
steps:
- name: Checkout
uses: actions/checkout@v4
# ============ 后端 API ============
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
- name: Build delivery-platform-api
run: |
mvn -f services/pom.xml -pl delivery-platform-api -am -DskipTests clean package -q
- name: Build API Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:latest \
services/delivery-platform-api
# ============ Webhook ============
- name: Build license-webhook-ingress
run: |
mvn -f services/pom.xml -pl license-webhook-ingress -am -DskipTests clean package -q
- name: Build Webhook Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:latest \
services/license-webhook-ingress
# ============ 前端 ============
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Build frontend
working-directory: web/delivery-platform-ui
run: |
npm install
npm run build
- name: Build Frontend Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:latest \
web/delivery-platform-ui
# ============ 推送镜像到 Gitea Registry ============
- name: Login to Gitea Container Registry
run: echo "${{ secrets.GITEA_REGISTRY_TOKEN }}" | docker login gitea.craftlabs.cn -u "${{ secrets.GITEA_REGISTRY_USER }}" --password-stdin
- name: Push images
run: |
docker push ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.API_IMAGE }}:latest
docker push ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:latest
docker push ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:latest
# ============ 远程部署 ============
- name: Deploy via docker-compose
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
PLATFORM_JWT_SECRET: ${{ secrets.PLATFORM_JWT_SECRET }}
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN: ${{ secrets.WEBHOOK_TOKEN }}
run: |
# 将 docker-compose.yml 复制到部署目录并替换镜像版本
mkdir -p /opt/craftlabs/deploy
cp services/docker-compose.yml /opt/craftlabs/deploy/
cd /opt/craftlabs/deploy
export API_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.API_IMAGE }}:${{ github.sha }}
export WEBHOOK_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.WEBHOOK_IMAGE }}:${{ github.sha }}
export UI_IMAGE_TAG=${{ env.REGISTRY }}/${{ env.UI_IMAGE }}:${{ github.sha }}
docker compose pull
docker compose up -d --remove-orphans
+24
View File
@@ -0,0 +1,24 @@
version: 2
updates:
- package-ecosystem: maven
directory: "/services"
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: maven
directory: "/java"
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: npm
directory: "/web/delivery-platform-ui"
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
+2 -2
View File
@@ -2,9 +2,9 @@ name: ci-java
on:
push:
branches: [main, master]
branches: [main, master, develop]
pull_request:
branches: [main, master]
branches: [main, master, develop]
jobs:
maven:
+52
View File
@@ -0,0 +1,52 @@
name: ci-platform
on:
push:
branches: [main, master, develop]
paths:
- "services/**"
- "web/**"
- "contracts/**"
- ".github/workflows/ci-platform.yml"
pull_request:
branches: [main, master, develop]
paths:
- "services/**"
- "web/**"
- "contracts/**"
- ".github/workflows/ci-platform.yml"
jobs:
maven-services:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
- name: Forbid craftlabs-auth-bitanswer in platform tree
run: |
set -euo pipefail
TREE=$(mvn -f services/pom.xml -q -DskipTests dependency:tree -pl delivery-platform-api,license-webhook-ingress -am)
if echo "$TREE" | grep -q 'craftlabs-auth-bitanswer'; then
echo "::error::Banned dependency craftlabs-auth-bitanswer found in platform dependency tree"
echo "$TREE"
exit 1
fi
- name: Maven verify (platform services)
run: mvn -f services/pom.xml -B verify
web-ui-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: npm install and build
working-directory: web/delivery-platform-ui
run: |
npm install
npm run build
+53
View File
@@ -0,0 +1,53 @@
name: ci-security
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
workflow_dispatch:
jobs:
trivy-maven-modules:
name: Trivy (Java / Maven manifests)
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- scan-ref: services
- scan-ref: java
steps:
- uses: actions/checkout@v4
- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: fs
scan-ref: ${{ matrix.scan-ref }}
scanners: vuln
vuln-type: os,library
severity: CRITICAL,HIGH
exit-code: "1"
ignore-unfixed: true
npm-audit-ui:
name: npm audit (delivery-platform-ui)
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: web/delivery-platform-ui
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/delivery-platform-ui/package-lock.json
- name: Install and audit
run: |
npm ci
npm audit --audit-level=high
@@ -0,0 +1,39 @@
# 手动触发:打包 SDK + Native(.so) 并生成 SHA256SUMS,供 GitHub Release 上传
name: sdk-release-checksums
on:
workflow_dispatch:
jobs:
checksums:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
- name: Install native build deps
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Build native (.so)
run: |
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release -DCRAFTLABS_BUILD_JNI=ON
cmake --build native/build --parallel
- name: Maven package (SDK jars)
run: mvn -f java/pom.xml -B -DskipTests package
- name: Generate SHA256SUMS
run: |
chmod +x scripts/sdk-release-checksums.sh
./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release --native-path "${{ github.workspace }}/native/build"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sdk-release-${{ github.ref_name }}
path: |
dist/sdk-release/
java/craftlabs-auth-core/target/*.jar
java/craftlabs-auth-bitanswer/target/*.jar
java/craftlabs-auth-selfhosted/target/*.jar
+8
View File
@@ -1,3 +1,6 @@
# SDK 发布产物(校验和脚本生成,不上库)
dist/
# Build
**/build/
**/target/
@@ -34,8 +37,13 @@ cmake-build-*/
# Maven
dependency-reduced-pom.xml
# Node (平台前端)
node_modules/
web/**/dist/
# Python
__pycache__/
*.py[cod]
.venv/
venv/
.env
+79
View File
@@ -0,0 +1,79 @@
# PROJECT KNOWLEDGE BASE
**Generated:** 2026-05-26
**Commit:** 4913d1c
**Branch:** develop
## OVERVIEW
**craftlabs-authorization-sdk** — 创飞客户端授权 SDK 工作区。多语言 monorepoJava (Maven) 封装授权 API + Rust (Cargo) native cdylib + Vue 3 交付管理后台 + Spring Boot 后端服务。37k+ 行源码,活跃开发中。
## STRUCTURE
```
./
├── java/ # Maven 多模块 SDK (core, bitanswer, selfhosted, tests)
├── native/ # Rust Cargo workspace (craft-core cdylib, CLI tool)
├── services/ # Spring Boot 后端服务
│ ├── delivery-platform-api/ # 商业交付管理 API (153 Java 文件)
│ └── license-webhook-ingress/ # Webhook 回调入口 (小)
├── web/
│ └── delivery-platform-ui/ # Vue 3 前端 (47 src 文件)
├── schemas/ # craftlabs-auth-config JSON Schema
├── examples/ # 示例配置 (java/cpp/python/vc)
├── docs/ # 产品/流程/工程架构文档
│ └── engineering/ # 系统架构、工程边界、并行迭代
└── engineering/ # 工作区 manifest, 规划工程占位
```
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
| SDK 授权核心逻辑 (Java) | `java/craftlabs-auth-core/src/` | config, internal 模块 |
| 比特安索集成 | `java/craftlabs-auth-bitanswer/` | 单一 Java 文件 |
| 自托管授权提供者 | `java/craftlabs-auth-selfhosted/` | 同上 |
| Rust native C ABI | `native/craft-core/src/` | lib.rs 导出 craft_* 函数 |
| 安全反调试/混淆 | `native/craft-core/src/security/` | anti_debug, obfuscation |
| CLI 工具 | `native/craftlabs-auth-cli/src/` | status/activate/check/info 命令 |
| 平台后端 Controller | `services/delivery-platform-api/` | 按领域分包 (contract, license, device 等) |
| 平台持久层 | `services/delivery-platform-api/` | persistence/ 下每实体一对 (POJO+Mapper) |
| 平台 DTO | `services/delivery-platform-api/` | web/dto/ 下 47 个请求/响应类 |
| Webhook 回调 | `services/license-webhook-ingress/` | webhook 入口 + persistence |
| 前端视图 | `web/delivery-platform-ui/src/views/` | Vue 3 组件 (38 文件) |
| 数据库迁移 | `services/delivery-platform-api/` | src/main/resources/db/migration/ |
| JSON Schema | `schemas/` | craftlabs-auth-config 校验 |
| CI/CD (Gitea Actions) | `GITEA_CI_CD.md` | act_runner 配置 |
## CONVENTIONS
- **Java**: Spring Boot 3.x, MyBatis-Plus, Maven multi-module. 每实体一对 `Entity` + `Mapper` 接口。控制器统一 `@RestController` + `@RequestMapping("/api/v1/...")`. 异常处理统一 `ApiExceptionHandler`.
- **Rust**: cdylib 导出 `craft_*` C ABI。`Provider` trait 模式。安全模块独立 `security/` 子树。
- **Vue**: Vue 3 + Composition API (`<script setup>`). 视图集中 `src/views/`.
- **SQL**: Flyway 迁移在 `db/migration/`, 前缀 `V{version}__{description}.sql`.
- **发布**: SDK 发版生成 SHA256SUMS + 可选 GPG 签名, 见 `java/RELEASING.md` + `scripts/sdk-release-checksums.sh`.
## ANTI-PATTERNS (THIS PROJECT)
- **不要** 把 SDK jar 打进平台 Fat JAR (bootstrap 唯一 repackage)
- **不要** 在客户端 SDK 中依赖平台后端代码 (运行时解耦)
- **不要** 混用 JNI 和 JNA 桥接 — 当前已迁移到 JNA
## COMMANDS
```bash
# Java SDK 构建 (JDK 17+)
mvn -f java/pom.xml clean verify
# Rust native 构建 (Rust 1.70+)
cargo build --manifest-path native/craft-core/Cargo.toml --release
# 前端
cd web/delivery-platform-ui && npm install && npm run build
```
## NOTES
- 平台后端/前端按架构设计为独立工程,本仓库含源码仅为过渡期方便迭代。正式部署时按 `engineering/planned/` 规范开独立仓。
- native/craft-core 曾用 JNI bridge,现已迁移至 JNA (commit 027ecbd)。
- 有三轨并行迭代:后端/前端/SDK,详见 `docs/engineering/PARALLEL_ITERATION_INDEX.md`
+118
View File
@@ -0,0 +1,118 @@
# Gitea CI/CD 配置指南
## 1. Gitea Actions Runner 注册
### 1.1 部署 Runner
```bash
# 从 Gitea 管理后台获取 runner 注册令牌
# 位置:站点管理 -> 运行 Actions -> 创建 Runner
# 创建 runner 数据目录
mkdir -p /opt/gitea-runner
cd /opt/gitea-runner
# 下载 act runner
curl -sL https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64 -o act_runner
chmod +x act_runner
# 注册 runner(替换 TOKEN 和 GITEA_URL
./act_runner register \
--instance https://gitea.craftlabs.cn \
--token <REGISTRATION_TOKEN> \
--name craftlabs-runner \
--labels ubuntu-latest:docker://node:20-bookworm
# 以服务方式运行
./act_runner daemon &
```
### 1.2 Runner 标签说明
| 标签 | 用途 | 对应的 workflow `runs-on` |
|------|------|--------------------------|
| `ubuntu-latest` | 通用构建和测试 | `ubuntu-latest` |
## 2. 配置 Gitea Secrets
在 Gitea 仓库 Settings -> Secrets 中添加:
| Secret 名称 | 说明 |
|-------------|------|
| `GITEA_REGISTRY_TOKEN` | Gitea Container Registry 访问令牌 |
| `GITEA_REGISTRY_USER` | Registry 用户名 |
| `DB_PASSWORD` | PostgreSQL 数据库密码 |
| `PLATFORM_JWT_SECRET` | JWT 签名密钥(至少 32 字符)|
| `WEBHOOK_TOKEN` | Webhook x-bitanswer-token |
## 3. 推送仓库到 Gitea
```bash
# 添加 Gitea 远程仓库
git remote add gitea https://gitea.craftlabs.cn/craftlabs/authorization-sdk.git
# 推送到 Gitea
git push -u gitea develop
# 推送到 Gitea 并设为主分支
git push gitea develop:main
```
## 4. CI 流程说明
### 4.1 提交触发
| Workflow | 触发条件 | 运行内容 |
|----------|---------|---------|
| `ci-java` | push/PR to main/develop | Maven verify + Native 编译 |
| `ci-platform` | push/PR to main/develop (services/web) | Maven verify + npm build |
| `ci-security` | push/PR to main/develop | Trivy 漏洞扫描 + npm audit |
| `deploy` | push to main | 构建 Docker 镜像 → Gitea Registry → docker-compose 部署 |
### 4.2 手动触发
| Workflow | 触发方式 |
|----------|---------|
| `sdk-release-checksums` | 仓库 Actions 页面手动触发 |
| `deploy` | 仓库 Actions 页面手动触发 |
## 5. 部署架构
```text
┌─────────────────────────────────┐
│ Gitea 仓库(craftsupport.cn
│ push main → Gitea Actions │
└──────────┬──────────────────────┘
│ 触发
┌──────────▼──────────────────────┐
│ Self-Hosted Runner │
│ ├── mvn package → Docker build │
│ ├── npm build → Docker build │
│ └── docker compose up -d │
└──────────┬──────────────────────┘
│ 部署
┌──────────▼──────────────────────┐
│ 部署主机(生产环境) │
│ ├── PostgreSQL 15 │
│ ├── delivery-platform-api:8080 │
│ ├── license-webhook-ingress:8081│
│ └── delivery-platform-ui:80 │
└─────────────────────────────────┘
```
## 6. 环境变量要求
部署时需确保以下环境变量已设置:
```bash
# 数据库
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/craftlabs_platform
SPRING_DATASOURCE_USERNAME=craftlabs
SPRING_DATASOURCE_PASSWORD=<实际密码>
# JWT
PLATFORM_JWT_SECRET=<至少32字符随机密钥>
# Webhook
CRAFTLABS_WEBHOOK_EXPECTED_TOKEN=<与比特控制台一致>
```
+43
View File
@@ -0,0 +1,43 @@
# CraftLabs Authorization SDK
创飞 **客户端授权 SDK** 工作区:Java API + Native 动态库 + **授权配置 JSON Schema** + 示例与文档。
**商业交付管理平台**(合同/交付/SN/Webhook 运营后台)按架构设计为 **独立仓库**,见 [`engineering/planned/`](engineering/planned/) 与 [`docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md`](docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
平台后端 **运维部署****单枚可执行 Fat JAR**(多模块源码、`bootstrap` 唯一 `repackage`);**切勿**与下方 **客户端 SDK** 工件混用或把 SDK 打进平台 JAR。
## 仓库结构
| 目录 | 说明 |
|------|------|
| [`java/`](java/) | Maven 多模块:`craftlabs-auth-core``craftlabs-auth-bitanswer``craftlabs-auth-selfhosted``craftlabs-auth-tests` |
| [`native/`](native/) | **Rust Cargo workspace**`craft-core` cdylib 导出 `craft_*` C ABI(对齐 `docs/平台架构思路.md`);旧 C++ CMake 见 `.deprecated-cmake/` |
| [`schemas/`](schemas/) | `craftlabs-auth-config` JSON Schema |
| [`examples/`](examples/) | 示例配置与烟测脚本 |
| [`docs/`](docs/) | 比特对接、创飞平台产品/流程/工程架构文档 |
| [`engineering/`](engineering/) | 工作区 manifest、**规划工程占位** |
## 构建
```bash
# Java(需 JDK 17+
mvn -f java/pom.xml verify
# Rust 核心库(需 Rust 1.70+
cargo build --manifest-path native/craft-core/Cargo.toml --release
# 产物:native/target/release/libcraftlabs_auth_bitanswer.{so,dylib,dll}
```
## 发布与完整性(SHA-256 / GPG
对外发版前生成 **`SHA256SUMS`**、可选 **GPG 签 JAR****`SHA256SUMS.asc`**,见 **[`java/RELEASING.md`](java/RELEASING.md)**(脚本:`scripts/sdk-release-checksums.sh`)。
## 文档索引
- 工程划分与边界(架构):[`docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md`](docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)
- 三轨并行迭代(后端/前端/SDK):[`docs/engineering/PARALLEL_ITERATION_INDEX.md`](docs/engineering/PARALLEL_ITERATION_INDEX.md)
- 平台功能模块:[`docs/chuangfei-platform-product-modules.md`](docs/chuangfei-platform-product-modules.md)
- 业务流程与版本排期:[`docs/chuangfei-platform-bpm-and-roadmap.md`](docs/chuangfei-platform-bpm-and-roadmap.md)
- 比特授权与规则纲要:[`docs/bitanswer-licensing-design-and-rules.md`](docs/bitanswer-licensing-design-and-rules.md)
## 许可证
版权所有 © 广州创飞人工智能技术有限公司(以项目实际声明为准)。
+27
View File
@@ -0,0 +1,27 @@
# 契约(Contracts
## OpenAPI — 交付平台 API
| 文件 | 说明 |
|------|------|
| [`openapi/delivery-platform-api.json`](openapi/delivery-platform-api.json) | **`delivery-platform-api` 的单一事实来源(SSOT)**;与运行时 `/v3/api-docs` 对齐。 |
### 更新快照(维护者)
在仓库根目录或 `services/delivery-platform-api` 下执行(需 JDK 17):
```bash
export JAVA_HOME=# JDK 17+
cd services/delivery-platform-api
UPDATE_OPENAPI=1 mvn -q test -Dtest=OpenApiContractSnapshotTest
```
提交前请 **审阅 diff**:破坏性变更需 bump 版本说明、同步前端与集成方。
### CI 校验
默认 `mvn verify` 会运行 `OpenApiContractSnapshotTest`:**运行时生成的 OpenAPI 与快照须一致**。若仅改实现未改契约却导致文档变化,应更新快照并写在 PR 说明中。
### 与前端
`web/delivery-platform-ui` 的 axios 路径应与 OpenAPI `paths` 一致;可选后续接入 OpenAPI Generator 生成 TS 类型(非必选)。
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
# `initialize(config_json)` 配置说明
`AuthProvider.initialize(String)` 与 C API `auth_initialize(const char* config_json)` 共用同一 JSON 语义,版本由 **`schemaVersion: 1`** 锁定。
## 规范与示例
| 资源 | 路径 |
|------|------|
| JSON Schema | `schemas/craftlabs-auth-config.schema.json` |
| 码头 / 集团拓扑示例 | `examples/config/wharf.bitanswer.json` |
| 学校 / 边设备示例 | `examples/config/school.bitanswer.json` |
| 流动人口 / 项目示例 | `examples/config/floating.bitanswer.json` |
| 学校 / 自研 HTTP 示例 | `examples/config/school.selfhosted.json` |
## Java 解析与校验
```java
import cn.craftlabs.auth.config.AuthConfig;
import cn.craftlabs.auth.config.AuthConfigException;
import cn.craftlabs.auth.config.AuthConfigs;
AuthConfig cfg = AuthConfigs.parse(jsonString);
// 再交给实现(与校验使用同一字符串即可)
provider.initialize(jsonString);
```
`AuthConfig` 提供 `bitanswerFeatureId(String logicalKey)` 等辅助方法,供后续在 `hasFeature` 与比特 `QueryFeature` 之间做映射。
## 场景字段摘要
- **wharf**`wharf.topology``cloud` / `group` / `local_float`)等与集中授权拓扑相关的**提示**;实际连网方式仍以 `bitanswer.url` 为准。
- **school**`school.edgeDeviceId` 等用于运营对账;是否写入比特自定义字段由实现阶段决定。
- **floating****必填** `floating.projectId`;表达「按项目」授权锚点,与商务合同、控制台业务模板需一致。
后续优化(错误码、OIDC、集团占点策略等)在保持 `schemaVersion` 或递增版本的前提下扩展字段即可。
@@ -0,0 +1,88 @@
# 比特安索方案:工作区走查 — 地址与「回调」评估
> **范围**:本仓库内与比特安索(Bitanswer)相关的 URL、回调概念及受影响系统能力。
> **日期**2026-04-06
> **关联**[授权制度与规则](bitanswer-licensing-design-and-rules.md) · [客户端 API 纲要](bitanswer-client-api-overview.md) · [创飞侧平台与对接能力评估](chuangfei-bitanswer-integration-platform.md)
---
## 1. 结论摘要
| 类别 | 是否在本仓库配置 | 典型形态 | 涉及系统功能 |
|------|------------------|----------|--------------|
| **`bitanswer.url`(授权服务地址)** | 是(JSON 配置 + Schema | `https://…` 云/E3、`bit://ip:port` 集团、`lic://…` 本地等 | 登录、在线激活/升级、会话与心跳、特征项校验等与 **Bitanswer 运行时** 直连的能力 |
| **控制台「规则」HTTP Callback** | 否(官方控制台配置) | 业务系统提供的 `https://…` Webhook + `x-bitanswer-token` | **订单/资产/审计**:激活前后、设备绑定、云保保会话失效等事件的异步通知与集成 |
| **SDK 属性里的函数指针回调** | 未在本 SDK 封装层暴露 | C 侧 `SetAttr` + `ATTR_HB_*_CALLBACK` 等 | **进程内**心跳失败/停止、集团排队等待等 **UI 或重连策略**,非 HTTP |
| **`selfhosted.baseUrl`** | 是(另一 provider | 自研授权 HTTP 根地址 | 与比特方案并行;本仓库仅 `selfhosted_http_ping` 等占位,非 Bitanswer 官方回调 |
本 SDK 当前 **`BitAnswerProvider` 仅委托 native,未实现规则 Webhook 接收端**;若产品要在比特控制台配规则回调,需在 **独立后端服务** 实现 POST 接口与鉴权。
---
## 2. 本仓库中出现的「地址」清单
### 2.1 配置项:`bitanswer.url`
- **定义**[`BitanswerConfigSection`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/BitanswerConfigSection.java) 字段 `url`
- **校验**[`AuthConfigs`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigs.java) 在 `provider=bitanswer` 时要求非空。
- **Schema**[`schemas/craftlabs-auth-config.schema.json`](../schemas/craftlabs-auth-config.schema.json) 描述为 **Bit_Login `szURL`**(云 `http(s)://`、集团 `bit://`、本地 `lic://` 等)。
- **示例**
- [`examples/config/floating.bitanswer.json`](../examples/config/floating.bitanswer.json)、[`school.bitanswer.json`](../examples/config/school.bitanswer.json)`https://cloud.bitanswer.example/e3`(占位云地址)。
- [`examples/config/wharf.bitanswer.json`](../examples/config/wharf.bitanswer.json)`bit://license.example.com:8273`(集团服务)。
**功能映射(客户端 → 比特侧)**
- **登录与会话**`Bit_Login` / `Bit_LoginEx` 连到该 URL 所指云中心或集团服务;决定云/集团/混合行为还与 `loginMode`、环境变量等有关(见 `docs/bitanswer-client-api-overview.md` §23)。
- **在线激活与升级**:同类 URL 会传给 `Bit_UpdateOnline``Bit_GetUpdateInfo` 等(官方 API 表见 `docs/bitanswer-client-api-overview.md` §5)。
- **可选 `rootPath`**:配置项中有 `rootPath`,对应 `Bit_SetRootPath`,影响本地授权文件查找根目录(与 URL 形态 `root://` 语义相关,见 `docs/c.md`)。
### 2.2 文档中的规则 Callback URL(非本仓库代码配置)
- **出处**[`docs/bitanswer-licensing-design-and-rules.md`](bitanswer-licensing-design-and-rules.md) §5.5,对应官方 [规则](https://doc.bitanswer.cn/docs/function/rule/) 文档。
- **形态**:JSON 动作数组中的 `callback.url`,请求头可含 `x-bitanswer-token`
- **触发事件**(与业务系统相关):`sn:pre_activate` / `sn:post_activate``device:pre_activate` / `device:post_activate``yunbaobao:session_logout` 等。
**功能映射(贵司后端若承接回调)**
| 事件 | 建议评估的系统能力 |
|------|---------------------|
| `sn:pre_activate` | 激活前风控:黑名单、订单状态、SN 与合同是否一致;可配合异步拒绝策略(以实现为准) |
| `sn:post_activate` | 开通交付、CRM 状态更新、用量计费起点、通知客户 |
| `device:pre_activate` / `post_activate` | 设备台账、换机策略、终端数/浮动策略与内部资产系统对账 |
| `yunbaobao:session_logout` | 会话吊销联动:踢下线、清理服务端会话、安全审计 |
**注意**:官方说明回调为 **异步 POST****响应体可被忽略**;接收方应 **幂等**、校验 `x-bitanswer-token`、限流与防重放。
### 2.3 SDK 文档中的「回调」= C 函数指针(非 URL)
- **出处**[`docs/c.md`](c.md) 中 `Bit_SetAttr` 相关常量,例如:
- `ATTR_HB_STOPED_CALLBACK` / `ATTR_HB_STOPED_CALLBACK_EX`:心跳停止
- `ATTR_HB_RETRY_FAILED_CALLBACK` / `ATTR_HB_RETRY_CALLBACK_EX` / `EX2`:心跳重试失败/重试
- `ATTR_HB_RETRY_SUCCESS_CALLBACK`:心跳重试成功
- `ATTR_QUEUE_WAIT_CALLBACK`:集团排队等待(与 `BIT_QUERY_WAIT` 等配合)
**功能映射**:在 **同一进程内** 由 Bitanswer 客户端库调用开发商注册的函数,用于 **监控与授权服务的连接质量、排队 UX、自动重连**
**本仓库**[`BitAnswerProvider`](../java/craftlabs-auth-bitanswer/src/main/java/cn/craftlabs/auth/bitanswer/BitAnswerProvider.java) 未暴露 `setAttr`/这些回调;若需使用,要在 **native 层或后续 JNI** 扩展。
### 2.4 其它:`selfhosted.baseUrl`
- **定义**[`SelfhostedConfigSection`](../java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SelfhostedConfigSection.java)。
- **代码**[`native/src/selfhosted/http_client.h`](../native/src/selfhosted/http_client.h) 中 `selfhosted_http_ping(base_url)`
- **说明**:属于 **自研授权 provider**,与比特控制台规则 Callback **无直接对应**;评估部署时不要与 `bitanswer.url` 混淆。
---
## 3. 对实现与运维的检查项建议
1. **`bitanswer.url`**:区分环境(云/E3 生产、测试集团 `bit://`、纯本地 `lic://`);与 **产品识别码 `applicationData`**、**SN** 一并纳入密钥与配置管理。
2. **规则 Callback URL**:在架构图中单独标注为 **比特云 → 贵司公网/内网穿透 HTTPS**;明确 **谁生成 `x-bitanswer-token`**、轮换与日志脱敏(含 `sn``mid`、IP)。
3. **函数指针回调**:若未来在 native 启用,明确线程模型与 **不得在回调中执行阻塞 IO** 等约束(以官方 `bitanswer.h` 为准)。
4. **文档与代码一致性**:当前 [`native/src/bitanswer/bitanswer_adapter.h`](../native/src/bitanswer/bitanswer_adapter.h) 仍为占位;真正接入 `Bit_Login` 等后,应同步更新本文 §2.1 的实现引用。
---
## 4. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:基于工作区 grep 与核心配置/Java/native 文件走查。 |
@@ -0,0 +1,188 @@
# 比特授权云:授权制度设计与规则机制(实现依据)
> **文档来源**(官方):
>
> - [授权设计与创建](https://doc.bitanswer.cn/docs/function/licensing-design-creation/)
> - [规则](https://doc.bitanswer.cn/docs/function/rule/)
>
> **用途**:作为本仓库及相关功能**设计、实现、联调**时的领域依据;与更宽范围的机制总览见 `[bitanswer-authorization-overview.md](./bitanswer-authorization-overview.md)`。
> **整理日期**2026-04-06
> **对接落地**:创飞侧平台、交付物与能力评估见 [chuangfei-bitanswer-integration-platform.md](./chuangfei-bitanswer-integration-platform.md)。
---
## 1. 授权制度:四层对象
授权由四层对象叠加定义「卖什么、怎么卖、发什么」。
| 层级 | 作用 |
| -------------------- | ------------------------------------------------------------------------------- |
| **产品 (Product)** | 定义被授权对象:一套软件或组合;承载**授权功能模块的集合**。含唯一产品名、**特征项**(功能/加密点)、**配置项**(应用与用户小数据)。 |
| **模版 (Template)** | 产品特征项的**子集**,限定某次授权里**可用模块及版本**,常对应一个**发行版本**。 |
| **业务 (Business)** | 定义**销售/授权商业模式**:云授权、集团授权、单机授权等;在此配置各类**授权属性组合**。 |
| **授权 (Entitlement)** | 实际发给用户的授权:**从模版继承模块**;**从业务继承授权类型**,但可**改写**业务允许的授权属性。由全球唯一 **16 位字母数字 SN** 标识。 |
### 1.1 授权内容更新与同步
- 授权内容更新后,客户端可用对应 **SN** 升级授权内容。
- **云授权**:更新可较快反映到客户端,应用**无需重启**。
- **集团授权、单机授权**:更新在客户端**下次连接服务器**时同步。
- **单机**:带**智能连接**的会在联网时自动同步;带**强制认证**的需定期连服务器;其它情况多在授权失效时才尝试连服务器。
- 开发商也可选择**手动**更新客户端本地授权内容。
---
## 2. 业务侧授权属性(约束维度)
在业务中可配置的约束项(不同授权类型下可用组合不同),官方文档列出的包括:
| 属性 | 说明 |
| --------------- | ----------------------------------- |
| **连接类型**(单机) | 离线,或可自动进行后台验证的**智能连接**。 |
| **强制认证** | 客户端**最大离线时间**;超过后须与服务器再次验证才能继续使用。 |
| **安装限制** | 客户端允许**激活次数**;单机授权下由**所有客户端共享**该次数。 |
| **有效期** | 从激活起算的最长使用时间。 |
| **用户数** | 集团授权:集团服务器可支持的**最大并发访问用户数**。 |
| **起始日期 / 结束日期** | 授权生效、失效的**绝对时间**。 |
| **使用次数** | 客户端能成功执行 **Login** 的次数上限。 |
| **终端限制** | 单机授权允许在多少台客户端上被激活。 |
通过不同业务(例如演示业务、永久有效单机正式版业务)组合上述属性,对齐销售与交付形态。
---
## 3. 特征项 (Feature):授权在代码中的语义
- **标识**:特征 **ID**(整数,API 调用用)+ **名称**(界面展示);在产品内唯一。
- **产品级**:数量、名称、类型、是否**可覆盖**、特征组等**仅能在产品特征项界面**完成。
- **模版 / 授权码级**:只能**选择**特征项、设置特征项的**授权属性**,或修改带「可覆盖」的特征项**值**。
### 3.1 特征项在产品上的基本属性
- **ID**:系统分配,用于客户端 API 指定特征项。
- **名称**:用途说明;在集团授权管理界面等对用户可见。
- **类型**:决定可进行的操作(见下表)。
- **可覆盖**:是否允许在模版或授权码中**重写该特征项的值**(不同用户/模版需要不同特征值时应开启)。
- **值**:影响特征项 API 行为。
### 3.2 授权码上的特征项授权属性
在授权码中可设置/修改(且同时受**整条授权**的授权属性约束):
- **有效期**:自所在授权码激活起算,单位**天**。
- **结束日期**:可用到结束日当天 24 点。
- **用户数**:仅**集团授权**类型 SN 可设,为该特征项的可用用户数。
### 3.3 特征项类型与 API
| 类型 | 含义 | 主要 API |
| ------ | ----------- | --------------------------------- |
| **只读** | 只读数据,客户端不可写 | `ReadFeature` |
| **读写** | 可读可写 | `ReadFeature``WriteFeature` |
| **算法** | 算法因子,不可直接读写 | `ConvertFeature`(单向转换) |
| **密钥** | AES 密钥因子 | `EncryptFeature``DecryptFeature` |
所有类型均支持 `**QueryFeature`**、`**ReleaseFeature**`:检查是否存在及是否有效。
**集团授权**`QueryFeature` 会占用模块用户数,`ReleaseFeature` 释放;文档要求二者在代码中**成对**出现。
### 3.4 特征组
- 用于管理与勾选;默认有 **「所有」** 组包含全部特征项。
- 可新建组并向组内添加特征项;**新特征项默认不会**进入除「所有」外的组,需手工维护。
### 3.5 实现与安全注意
- 典型用法:每需单独授权的模块分配**主特征项**做 `QueryFeature` / `ReleaseFeature`;辅以加密类特征项;模块退出前释放占用(集团场景尤其重要)。
- 避免**过于频繁**调用特征项 API,以免影响性能。
- **DRM 产品**:特征项类型不可设置,且不可选「可覆盖」(用于内容加密密钥)。
- **密钥 / 算法**类型:操作员仅读权限时特征值不可见,显示为 `**`**。
---
## 4. 配置项 (Data):非安全存储
- **用途**`Name` / `Value` 字符串对,存应用配置或少量运行时数据;**不应**代替特征项做安全控制。
- **与特征项区别**:特征项侧重功能点/加密点;配置项侧重应用与用户数据。
- **覆盖**:模版或授权码中可创建**产品中未预先定义**的配置项名;若与产品配置项同名则运行时取得**覆盖后**的值。
- **删除**:可删除授权码中的配置项;**删除产品或模版中定义的配置项会返回错误**。
- **枚举**`GetDataNum``GetDataName` 等。
- **可靠性**:客户端库可能**缓存**配置项;异常断电等情况下**近期更新可能丢失**,应用需容错。存储为 **UTF-8**,含中文时客户端需注意编码处理。
---
## 5. 规则 (Rule):云端可编程扩展
与静态「产品—模版—业务—授权」模型并列,**规则**用于在特定**事件**上注入 **JavaScript 判断 + 动作**,扩展业务逻辑。
### 5.1 结构
| 组成部分 | 说明 |
| ------- | ------------------------------------------- |
| **事件** | 规则入口;向触发器提供 `event` 对象(不同事件字段不同)。 |
| **触发器** | JavaScript 编写的条件;返回 **布尔**`true` 时执行动作。 |
| **动作** | 触发后执行的操作(如回调);一条规则可有多个动作,**执行顺序不严格保证**;可为空。 |
**约束**:一条规则**仅对应一个事件**;创建后需在列表中**启动**方会在下次事件发生时生效。
### 5.2 已定义事件(文档列表)
| ID | 代码 | 名称 |
| ------ | -------------------------- | -------------------------- |
| 0x0001 | `yunbaobao:session_logout` | 云保保客户端会话失效(登出、踢出、过期) |
| 0x0101 | `sn:pre_activate` | 授权码**首次**激活**前** |
| 0x0102 | `sn:post_activate` | 授权码**首次**激活**后** |
| 0x0301 | `device:pre_activate` | 设备激活**前**(设备**重新激活**时也会执行) |
| 0x0302 | `device:post_activate` | 设备激活**后** |
### 5.3 事件载荷要点(实现联调参考)
- 多类事件含 `**snInfo`**`sn``type`(如 FLOAT)、`volumeNumber`(终端限制)、`activeDate``startDate``endDate`(文档说明为与 `expirationDays` 合计相关)、`expirationDays``userNumber``transferVolume``transferNumber``status``customInfo``ip` 等。
- `**sn:post_activate**``**device:post_activate**` 等还含 `**device**``regDate``mid`(设备指纹)、`status``deviceCount`(已激活设备总数)等。
- `**yunbaobao:session_logout**``sn``sid``type`LOGOUT / EXPIRED / CLEAR / OTHER)、`logout_time` 等。
详细字段以官方文档各事件小节为准。
### 5.4 触发器运行环境
- 语言:**ECMAScript 5.1****禁止使用 `const``let`**。
- **无**任意 IO**无** `console.log`**无** `window``document``location` 等。
- 保存前应用控制台**调试功能**验证脚本。
### 5.5 动作:回调 (Callback)
- 配置为 **JSON 数组**;元素示例形态包含 `callback.url``callback.headers`(如 `x-bitanswer-token` 用于认证)。
- **异步**执行;服务端响应**可被忽略**。
- 使用 **POST****JSON** body;内容包含 `event``rule``data`(含对应事件的 `snInfo` / `device` / `ip` 等)。
---
## 6. 与实现工作的对应关系(摘要)
| 领域概念 | 实现侧通常对应 |
| ----------------- | ------------------------------------------ |
| 产品 / 模版 / 业务 / SN | 控制台配置与发放流程;SDK 登录对象与产品绑定 |
| 业务授权属性 | 期限、终端、离线、次数、用户并发等策略的建模与校验 |
| 特征项类型与 API | 功能开关、加密点、集团用户数占用与释放 |
| 配置项 | 非敏感 K/V 同步;注意缓存与持久化边界 |
| 规则 | Webhook/内部服务对接激活与设备生命周期;ES5.1 脚本与回调幂等、安全校验 |
---
## 7. 修订记录
| 日期 | 说明 |
| ---------- | ------------------------- |
| 2026-04-06 | 初版:依据「授权设计与创建」「规则」两篇整理入库。 |
+71 -10
View File
@@ -1,12 +1,11 @@
# 比特授权云 · C 语言接口定义(离线摘录)
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:<https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/>
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:[https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/](https://doc.bitanswer.cn/docs/client-api/c-interface-definitions-v2/)
---
## 认证
### Bit_Login / Bit_LoginEx
```c
@@ -26,6 +25,7 @@ BIT_STATUS Bit_LoginEx(
BIT_HANDLE *pHandle,
LOGIN_MODE mode);
```
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
Bit_Login 等价于 Bit_LoginEx(…, featureId=0szReserved=NULL, …),当需要登录包含指定特征项的授权时才需要调用Bit_LoginEx。
@@ -50,6 +50,7 @@ Bit_Login 等价于 Bit_LoginEx(…, featureId=0szReserved=NULL, …),当
lic:///tmp,lic:///data,bit://127.0.0.1:8273
/tmp,/data,127.0.0.1:8273 // 等价第1条
```
> 注意:该接口支持通过[配置文件](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)和[环境变量](https://doc.bitanswer.cn/docs/client-api/examples-of-using-the-sdk/#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)来设置该值,如果同时设置优先级为:环境变量 > API传入 > 配置文件。
- **szSN** - [IN] 授权码(SN)字符串。如果为空(空串或NULL)则尝试寻找所有当前本机可用的SN。
@@ -57,10 +58,10 @@ lic:///tmp,lic:///data,bit://127.0.0.1:8273
| **类型** | **格式** | **说明** |
| -------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **授权码** | 比特授权云平台产生的SN 示例:`JNLTGZSE********` | 检查指定SN的License |
| **授权码** | 比特授权云平台产生的SN 示例:`JNLTGZSE******`** | 检查指定SN的License |
| **BIT-IDBIT-ID硬件,类似于加密的USB设备)** | 格式:#`<序号>`#bitid:`<序号>` 说明:`<序号>` 为 BIT-ID 的索引号,从 0 开始递增 示例:#0#1、#`bitid:0、#bitid:1` | 使用BIT-ID授权 |
| **激活口令(帐号授权密码或SN激活口令)** | <`xxx`> 通过“<>”包裹起来,中间部分为密码 示例:<1234>, <1233>65447> | 激活口令,是指授权码激活时,需要输入的一种口令,激活后不再需要,目前仅支持浮动授权 使用帐号授权时保存的是帐号授权的密码(已经不推荐),其它授权代表激活口令 |
| **既输入SN又输入激活口令** | `xxx<password>`,授权码后边紧跟“<>” 示例:`JNLTGZSE********`<123> | |
| **既输入SN又输入激活口令** | `xxx<password>`,授权码后边紧跟“<>” 示例:`JNLTGZSE******`**<123> | |
- **featureId** - [IN] 登录授权所需要包含的特征项ID。
@@ -73,6 +74,7 @@ lic:///tmp,lic:///data,bit://127.0.0.1:8273
<feature id="" name="" ver=""/>
</scope>
```
- **pApplicationData** - [IN] 产品识别码,在Bitanswer SDK头文件里。
- **pHandle** - [OUT] 通过Login函数返回的上下文句柄。
- **mode** - [IN] 登录模式,按位操作。
@@ -154,6 +156,7 @@ BIT_STATUS Bit_LoginByToken(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle)
```
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
### 参数
@@ -182,6 +185,7 @@ BIT_STATUS Bit_LoginByTokenEx(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle)
```
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
### 参数
@@ -213,6 +217,7 @@ BIT_STATUS Bit_LoginByPassword(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle);
```
通过用户名和密码登录帐号授权。
### 参数
@@ -231,6 +236,7 @@ BIT_STATUS Bit_LoginByPassword(
BIT_STATUS Bit_Logout (
BIT_HANDLE handle)
```
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
### 参数
@@ -247,6 +253,7 @@ BIT_STATUS Bit_Revoke (
BIT_CHAR *pRevocationInfo,
BIT_UINT32 *pRevocationInfoSize)
```
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
### 参数
@@ -264,6 +271,7 @@ BIT_STATUS Bit_RemoveSn (
BIT_PCSTR szSN,
BIT_UCHAR *pApplicationData)
```
删除指定授权码在本机的授权数据。
### 参数
@@ -278,6 +286,7 @@ BIT_STATUS Bit_Heartbeat(
BIT_HANDLE handle,
BIT_UINT32 *pReconnectsNum)
```
手动心跳,可以无限次调用,10s只会触发一次。
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
@@ -298,6 +307,7 @@ BIT_STATUS Bit_SessionControl(
BIT_CHAR *pValue,
BIT_UINT32 *pValueLen)
```
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
### 参数
@@ -325,6 +335,7 @@ BIT_STATUS Bit_SetSessionState (
BIT_UINT32 state,
BIT_VOID *pReserved)
```
设置客户端的状态为空闲状态或繁忙状态或激活状态。
### 参数
@@ -358,7 +369,6 @@ if (用户正在操作) {
## 激活升级
### Bit_UpdateOnline
```c
@@ -367,6 +377,7 @@ BIT_STATUS Bit_UpdateOnline (
BIT_PCSTR szSN,
BIT_UCHAR *pApplicationData)
```
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
### 参数
@@ -395,6 +406,7 @@ BIT_STATUS Bit_GetRequestInfo (
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。如果第一次调用返回错误码260,说明传入的pRequestInfoSize太小,可传入返回的pRequestInfoSize再重新调用一次。
### 参数
@@ -442,6 +454,7 @@ BIT_STATUS Bit_ApplyUpdateInfo(
BIT_CHAR *pReceipt,
BIT_UINT32 *pReceiptSize)
```
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
### 参数
@@ -475,6 +488,7 @@ BIT_STATUS Bit_ApplyUpdateInfoEx(
BIT_CHAR *pReceipt,
BIT_UINT32 *pReceiptSize);
```
应用升级码完成远程集团授权激活或升级。一般不建议使用。
### 参数
@@ -495,6 +509,7 @@ BIT_STATUS Bit_GetUpdateInfo (
BIT_CHAR *pUpdateInfo,
BIT_UINT32 *pUpdateInfoSize)
```
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
### 参数
@@ -508,7 +523,6 @@ BIT_STATUS Bit_GetUpdateInfo (
## 特征项操作
### Bit_BatchBegin
```c
@@ -516,6 +530,7 @@ BIT_STATUS Bit_BatchBegin(
BIT_HANDLE handle,
BIT_UINT32 mode)
```
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_BatchEnd接口批量提交连接集团服务。
### 参数
@@ -538,6 +553,7 @@ BIT_STATUS Bit_BatchEnd(
BIT_UINT32 *pResultList,
BIT_UINT32 *pResultListSize)
```
批量提交Query请求。
### 参数
@@ -577,6 +593,7 @@ BIT_STATUS Bit_QueryFeature (
BIT_UINT32 featureId,
BIT_UINT32 *pCapacity)
```
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
对于单机授权:只检查特征项是否可用。
@@ -610,6 +627,7 @@ BIT_STATUS Bit_ReleaseFeature (
BIT_UINT32 featureId,
BIT_UINT32 *pCapacity)
```
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
### 参数
@@ -643,6 +661,7 @@ BIT_STATUS Bit_QueryFeatureEx (
BIT_UINT32 *pCapacity,
BIT_PCSTR xmlScope)
```
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
### 参数
@@ -702,6 +721,7 @@ BIT_STATUS Bit_ReleaseFeatureEx (
BIT_UINT32 *pCapacity,
BIT_PCSTR xmlScope)
```
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
### 参数
@@ -740,6 +760,7 @@ BIT_STATUS Bit_QueryFeatureEx2 (
BIT_PCSTR xmlScope,
BIT_TICKET *pTicket)
```
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
对于单机授权:只检查特征项是否可用。
@@ -796,6 +817,7 @@ BIT_STATUS Bit_ReleaseFeatureEx2 (
BIT_TICKET ticket,
BIT_UINT32 consumed)
```
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
### 参数
@@ -811,6 +833,7 @@ if (status == BIT_SUCCESS) {
// 释放ticket占用的用户数
}
```
**特征项Query相关接口比较**
@@ -830,6 +853,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
BIT_PCSTR xmlScope,
BIT_INT32 *pExpired)
```
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
### 参数
@@ -845,6 +869,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
<!-- 或者直接输入:'1.1' -->
```
- **pExpired** - [OUT] 返回该特征项的剩余有效期,正数代表剩余有效期,负数代表过期天数,36500代表永久有效。
### Bit_GetFeatureInfoEx2
@@ -857,6 +882,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
BIT_CHAR *pFeatureInfo,
BIT_UINT32 *pFeatureInfoSize);
```
获取指定feature的信息,以XML格式返回。
### 参数
@@ -872,6 +898,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
<!-- 或者直接输入:'1.1' -->
```
- **pFeatureInfo** - [OUT] feature信息存储区地址。
- **pFeatureInfoSize** - [IN/OUT] 会话存储区大小。
@@ -884,6 +911,7 @@ BIT_STATUS Bit_GetTicketInfo(
BIT_CHAR *pXmlInfo,
BIT_UINT32 *pSize)
```
获取ticket信息。
### 参数
@@ -946,6 +974,7 @@ BIT_STATUS Bit_ConvertFeature(
BIT_UINT32 para4,
BIT_UINT32 *pResult);
```
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
### 参数
@@ -981,6 +1010,7 @@ BIT_STATUS Bit_EncryptFeature(
BIT_VOID *pCipherBuffer,
BIT_UINT32 dataBufferSize);
```
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
### 参数
@@ -1013,6 +1043,7 @@ BIT_STATUS Bit_DecryptFeature(
BIT_VOID *pPlainBuffer,
BIT_UINT32 dataBufferSize);
```
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
### 参数
@@ -1043,6 +1074,7 @@ BIT_STATUS Bit_ReadFeature(
BIT_UINT32 featureId,
BIT_UINT32 *pFeatureValue);
```
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
### 参数
@@ -1059,6 +1091,7 @@ BIT_STATUS Bit_WriteFeature(
BIT_UINT32 featureId,
BIT_UINT32 featureValue);
```
更新“读写”类型的特征项的数据内容。
### 参数
@@ -1069,7 +1102,6 @@ BIT_STATUS Bit_WriteFeature(
## 配置项操作
### Bit_GetDataItem
```c
@@ -1079,6 +1111,7 @@ BIT_STATUS Bit_GetDataItem (
BIT_VOID *pDataItemValue,
BIT_UINT32 *pDataItemValueSize)
```
读取指定的配置项数据。
### 参数
@@ -1097,6 +1130,7 @@ BIT_STATUS Bit_SetDataItem (
BIT_VOID *pDataItemValue,
BIT_UINT32 dataItemValueSize)
```
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
### 参数
@@ -1113,6 +1147,7 @@ BIT_STATUS Bit_GetDataItemNum (
BIT_HANDLE handle,
BIT_UINT32 *pNum)
```
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
### 参数
@@ -1129,6 +1164,7 @@ BIT_STATUS Bit_GetDataItemName (
BIT_CHAR *pDataItemName,
BIT_UINT32 *pDataItemNameSize)
```
根据配置项索引获取其名称,一般用于配置项的枚举操作。
### 参数
@@ -1145,6 +1181,7 @@ BIT_STATUS Bit_RemoveDataItem (
BIT_HANDLE handle,
BIT_PCSTR szDataItemName)
```
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
### 参数
@@ -1154,7 +1191,6 @@ BIT_STATUS Bit_RemoveDataItem (
## 信息获取
### Bit_GetSessionInfo
```c
@@ -1164,6 +1200,7 @@ BIT_STATUS Bit_GetSessionInfo (
BIT_CHAR *pSessionInfo,
BIT_UINT32 *pSessionInfoSize)
```
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
当函数返回BIT_ERR_BUFFER_SMALL时,pSessionInfoSize保存的是实际数据的大小。
@@ -1242,6 +1279,7 @@ BIT_STATUS Bit_GetInfo (
BIT_CHAR *pInfo,
BIT_UINT32 *pInfoSize)
```
获取本地授权信息。
如果要获取集团授权的信息,则szSN输入“`@bit://ip:port`”,会获取集团授权信息(仅支持type=BIT_INFO_SN)。
@@ -1281,6 +1319,7 @@ BIT_STATUS Bit_GetServerInfo(
BIT_CHAR *pServerInfo,
BIT_UINT32 *pServerInfoSize);
```
获取集团服务的License信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
### 参数
@@ -1321,6 +1360,7 @@ BIT_STATUS Bit_GetServerInfo(
……
</features>
```
1. 指定分组(group)查询
仅支持BIT_SERVER_INFO_SN_USERS、BIT_SERVER_INFO_FEATURE_USERS、BIT_SERVER_INFO_FEATURE_USERS_EX三种类型,通过name限定待查询的分组。
@@ -1332,6 +1372,7 @@ BIT_STATUS Bit_GetServerInfo(
</groups>
```
> 说明:通过Bit_SetCustomInfo的CUSTOM_GROUP_NAME(0xA)设置分组。
### 返回XML数据说明
@@ -1360,6 +1401,7 @@ XML返回示例:
BIT_STATUS Bit_GetVersion (
BIT_UINT32 *pVersion)
```
获取客户端安全库版本号。
### 参数
@@ -1373,6 +1415,7 @@ BIT_STATUS Bit_GetNextHandle(
BIT_HANDLE handle,
BIT_HANDLE *pNextHandle)
```
用来遍历当前进程内的handle。
### 参数
@@ -1398,6 +1441,7 @@ BIT_STATUS Bit_TestBitService (
BIT_UINT32 featureId,
BIT_UCHAR *pApplicationData)
```
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
### 参数
@@ -1421,6 +1465,7 @@ BIT_STATUS Bit_GetProductPath(
BIT_CHAR *pPath,
BIT_UINT32 lenPath);
```
获取授权存储目录。
### 参数
@@ -1434,6 +1479,7 @@ BIT_STATUS Bit_GetProductPath(
```c
BIT_STATUS Bit_GetLastError();
```
获取上一个API调用返回的错误码。
### 参数
@@ -1442,7 +1488,6 @@ BIT_STATUS Bit_GetLastError();
## 属性设置
### Bit_SetAttr
```c
@@ -1451,6 +1496,7 @@ BIT_STATUS Bit_SetAttr (
BIT_UINT32 type,
BIT_VOID *pValue)
```
设置全局配置或当前会话的配置。
### 参数
@@ -1516,6 +1562,7 @@ BIT_STATUS Bit_SetProxy (
BIT_PCSTR szUserID,
BIT_PCSTR szPassword)
```
设置代理服务的地址和端口。
### 参数
@@ -1540,6 +1587,7 @@ BIT_STATUS Bit_SetCustomInfo (
BIT_VOID *pInfoData,
BIT_UINT32 infoDataSize)
```
设置客户端运行自定义信息,需要在程序的最开始调用。
### 参数
@@ -1589,6 +1637,7 @@ if (status == BIT_SUCCESS) {
BIT_STATUS Bit_SetRootPath (
BIT_PCSTR szPath)
```
设置授权文件的存储路径。
### 参数
@@ -1604,6 +1653,7 @@ BIT_STATUS Bit_SetLocalServer (
BIT_UINT32 nPort,
BIT_UINT32 nTimeoutSecondes)
```
设置集团服务的地址和端口。
### 参数
@@ -1615,7 +1665,6 @@ BIT_STATUS Bit_SetLocalServer (
## 借出操作
### Bit_GetBorrowRequest
```c
@@ -1626,6 +1675,7 @@ BIT_STATUS Bit_GetBorrowRequest(
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
获取借出请求串,用来离线借出。
### 参数
@@ -1647,6 +1697,7 @@ BIT_STATUS Bit_GetBorrowFeatureRequest(
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
获取借出请求串,该接口支持指定特征项借出。
### 参数
@@ -1684,6 +1735,7 @@ BIT_STATUS Bit_CheckOutSnEx(
BIT_UCHAR *pApplicationData,
BIT_UINT32 nDurationDays);
```
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1721,6 +1773,7 @@ BIT_STATUS Bit_CheckOut(
BIT_UCHAR *pApplicationData,
BIT_UINT32 nDurationDays)
```
从授权服务器在线借出授权码或者特征项。
### 参数
@@ -1740,6 +1793,7 @@ type:借出类型。目前仅支持ws,表示从外网借出(针对云授
sn:指定借出的SN
-->
```
- **pFeatureList** xml结构,指定借出的特征项列表,如果不传则借出整个SN。
```xml
@@ -1749,6 +1803,7 @@ sn:指定借出的SN
<feature id='10'/>
</features>
```
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
@@ -1792,6 +1847,7 @@ BIT_STATUS Bit_CheckOutFeatures(
BIT_UINT32 nFeatureInList,
BIT_UINT32 nDurationDays);
```
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
### 参数
@@ -1831,6 +1887,7 @@ BIT_STATUS Bit_CheckIn(
BIT_UINT32 featureId,
BIT_UCHAR *pApplicationData)
```
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1848,6 +1905,7 @@ BIT_STATUS Bit_CheckInEx(
BIT_PCSTR szScope,
BIT_UCHAR *pApplicationData)
```
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1861,6 +1919,7 @@ BIT_STATUS Bit_CheckInEx(
<type>ws</type> <!-- type:归还类型,ws表示归还到云授权服务,默认是归还到集团授权服务 -->
</scope>
```
用于指定借出的参数,格式:wsxxxx,type设定为ws时表示从云授权服务器借出和归还。
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
@@ -1872,9 +1931,11 @@ BIT_STATUS Bit_ApplyBorrowInfo(
BIT_UCHAR *pApplicationData,
BIT_PCSTR pBorrowInfo)
```
客户端应用离线借出串,Bit_ApplyUpdateInfo可以兼容该接口。
### 参数
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
- **pBorrowInfo** - [IN] 借出请求串。
@@ -0,0 +1,391 @@
# 广州创飞人工智能技术有限公司 — 与比特安索对接:平台与交付物能力评估
> **平台定义**:**广州创飞人工智能技术有限公司客户商务与交付管理平台** —— 收口 **客户与项目、合同、交付产品、具体授权(含比特安索集成)** 等功能模块,支撑规模化商务履约与许可运营。
> **目的**:在 [回调与地址走查](bitanswer-callbacks-endpoints-assessment.md)、[授权制度与规则](bitanswer-licensing-design-and-rules.md) 等结论基础上,明确 **创飞侧需建设或提供的平台形态**、**具体交付软件**,以及 **应具备的能力**,用于立项、架构评审与验收。
> **日期**2026-04-06
> **说明**:比特安索侧能力以官方文档与控制台为准;本文聚焦 **创飞作为软件开发商 + 集成方** 的交付范围。
> **双视角分析**:§6 为 **产品经理** 维度,§7 为 **技术架构(与比特对接的抽象)** 维度。
> **功能清单**:模块划分与详细功能点见 [chuangfei-platform-product-modules.md](./chuangfei-platform-product-modules.md)。
> **业务流程与排期**:全业务 BPM、版本与迭代计划见 [chuangfei-platform-bpm-and-roadmap.md](./chuangfei-platform-bpm-and-roadmap.md)。
> **工程划分**:工作区目录边界与规划仓库见 [engineering/WORKSPACE_ENGINEERING_LAYOUT.md](./engineering/WORKSPACE_ENGINEERING_LAYOUT.md) · [根目录 engineering/](../engineering/README.md)。
> **实现与使用分档**:§8 为 **前端 + 后端****Java Spring Boot 4.0.\* 长期稳定线 + Vue 3 + Vite**)分阶段开发评估;§9 为 **产品使用**(MVP / Mid / Full)分阶段评估。后端 **固定主版本线为 Spring Boot 4.0.\***(补丁随官方 **4.0.x** 升级,**不用里程碑版**),以便与 **Spring 生态后续 AI 相关扩展**(如 Spring AI、统一配置与可观测性栈)同代演进,降低后续集成改造成本。
---
## 1. 对接边界(谁负责什么)
| 边界 | 比特安索(授权云 / 集团服务 / 控制台) | 创飞(本公司) |
|------|----------------------------------------|----------------|
| 授权模型 | 产品、模版、业务、SN、特征项、配置项、规则引擎 | 按产品线映射业务 SKU → 控制台配置;不在比特控制台外「再造一套许可模型」除非有自研 `selfhosted` |
| 客户端运行时 | 官方 Bitanswer SDKC/Java 等)、与产品绑定的库 | 集成 **craftlabs-authorization-sdk**(及随产品 native 库)、应用内调用与配置下发 |
| 出站 HTTP(比特 → 创飞) | 规则动作 **Callback**:向创飞 URL POST JSON | 提供 **公网可达、可鉴权** 的 HTTPS 端点;解析 `event` / `data` 并驱动内部流程 |
| 入站连接(创飞客户端 → 比特) | 云/E3 `https://…`、集团 `bit://…` 等 | 为每环境、每产品线配置正确 **`bitanswer.url`**;管理 SN、`applicationData`、证书与网络策略 |
---
## 2. 创飞建议提供的「平台」形态(逻辑分层)
不必一开始做成单一巨石系统,但建议在架构上区分以下 **逻辑平台**,便于分工与演进。
### 2.1 授权集成与配置平台(对内 DevOps / 架构)
- **能力**:统一维护「产品线 ↔ 比特产品/模版/业务/特征 ID」映射;生成与校验 [`craftlabs-auth-config`](../schemas/craftlabs-auth-config.schema.json) 类配置;管理多环境 `bitanswer.url`、SN 样例与联调账号。
- **交付形态**:可先从 **Git 仓库 + Schema CI 校验 + 文档** 起步,再演进为内部配置服务或门户。
### 2.2 许可事件与集成平台(对接规则 Callback)
- **能力**:接收比特控制台配置的 **规则 Callback**(异步 POST);校验 **`x-bitanswer-token`**(或双方约定头);按 `event` 类型路由到订单、资产、计费、通知等系统;**幂等**与**重试友好**(官方不依赖响应体)。
- **交付形态**:独立 **BFF/API 网关后的 Webhook 服务**(建议与核心业务库解耦,用消息队列削峰)。
### 2.3 客户与订单主数据(CRM / ERP / 自研商务系统)
- **能力**:SN 与合同/订单/客户绑定;激活前校验(对应 `sn:pre_activate` 场景);激活后状态推进(对应 `sn:post_activate`)。
- **说明**:可复用现有系统;若无,至少需 **最小订单-SN 台账** 才能用好规则回调。
### 2.4 设备与终端治理(可选但强烈建议)
- **能力**:存储 `mid`(设备指纹)与 SN、客户、场站关系;支持换机、终端数对账(对应 `device:pre_activate` / `post_activate`)。
- **适用**:单机浮动、多终端限制、现场交付类产品(如码头、校园场景示例配置所暗示的部署形态)。
### 2.5 可观测性与支持
- **能力**:客户端授权失败日志脱敏上报;区分「网络 / SN 无效 / 特征未授权 / 集团满并发」等;与比特控制台告警协同。
- **交付形态**:日志规范、仪表盘、客服查询 SN 状态的手册(只读控制台或内部工具)。
---
## 3. 具体交付软件与工件(建议清单)
下列项可与当前仓库及产品线对齐,按项目裁剪。
| 交付物 | 说明 | 与现有仓库关系 |
|--------|------|----------------|
| **craftlabs-authorization-sdk**(及绑定 native | 统一 Java/Native 授权门面,`provider=bitanswer` 时消费 `bitanswer.url` 等 | 本仓库已有核心模块与示例配置 |
| **产品线授权配置文件** | 每产品/场景的 JSON(含 `features.*.bitanswerFeatureId` 等) | 见 `examples/config/*.bitanswer.json` 模式 |
| **规则 Callback 接收服务** | HTTPS 端点 + 鉴权 + 事件分发;可选管理后台查看原始 payload(脱敏) | **本仓库未包含**,需单独工程 |
| **配置与安全交付流程** | SN、`applicationData`、环境 URL 的分发与轮换;禁止硬编码进镜像 | 流程 + 密钥管理(Vault/KMS/CI Secret |
| **集成与联调文档** | 环境矩阵、比特控制台截图级操作说明、错误码对照 | 可延续 `docs/bitanswer-*.md` 体系 |
| **(选用)集团授权服务器侧运维** | 若使用 `bit://` 自建集团服务:高可用、备份、与比特版本兼容矩阵 | 与 `wharf.bitanswer.json` 类场景相关 |
---
## 4. 应具备的能力清单(功能与非功能)
### 4.1 功能能力
| 能力域 | 应具备要点 |
|--------|------------|
| **运行时接入** | 能使用官方 SDK + 本 SDK 完成 Login、激活、特征项 Query/Release(集团场景成对释放)、心跳策略与产品识别码一致 |
| **配置治理** | Schema 级校验、环境隔离、特征 ID 与逻辑功能键映射可追溯 |
| **规则 Callback** | 支持文档所列事件的 JSON 解析;与订单/资产状态机衔接;可选拒绝或标记风险(视比特侧是否支持同步阻断,以官方为准) |
| **会话与吊销联动** | 若使用云保保相关能力:能消费 `yunbaobao:session_logout` 或与客户端登出联动,避免「业务已关、许可仍显示占用」类体验问题 |
| **多授权模式共存** | 云 / 集团 / 单机(含智能连接、强制认证)在架构上可区分部署与运维责任,避免混用 URL 与 loginMode |
### 4.2 非功能能力
| 能力域 | 应具备要点 |
|--------|------------|
| **安全** | Callback URL 仅 HTTPStoken 轮换;payload 脱敏日志;防重放与 IP 允许列表(若比特出口 IP 可约定) |
| **可靠性** | Webhook 快速 `2xx`、内部异步处理;队列重试;死信与人工补偿 |
| **合规与审计** | 激活、换机、会话失效留痕;满足客户与内部审计查询 |
| **版本与兼容** | 比特 SDK 与集团服务版本矩阵;升级窗口与客户通知 |
---
## 5. 与比特控制台的操作分工(落地检查表)
- **创飞运营/产品**:维护产品、模版、业务、特征项;配置规则与 **Callback URL**、**x-bitanswer-token**。
- **创飞研发**:实现客户端集成、Webhook 服务、内部状态机与对账。
- **创飞运维**`bitanswer.url` 网络可达性、证书、集团服务(若自建)SLA。
- **双方联调**`sn:pre_activate` / `post_activate` 各至少一轮全链路;含异常 SN、重复激活、设备重激活。
---
## 6. 产品经理维度:分析与评估
### 6.1 平台定位与价值主张
**客户项目、合同、交付物与授权** 收口为一条可追溯的业务链,并与比特安索在 SN、激活、规则回调、功能开关等能力上打通。对内减少「合同已签但授权未配、交付与许可不一致、规则回调无人跟进」等断层;对外使 **交付可预期、授权可运营、问题可定位**,支撑多产品线、多客户的规模化商务与交付,而非仅依赖人工台账与零散工具。
### 6.2 目标用户与人设(内部)
| 人设 | 典型职责 | 与平台关系 |
|------|----------|------------|
| 商务 / 客户经理 | 商机、报价、合同推进、客户沟通 | 维护客户与项目主数据,触发交付与授权需求 |
| 交付 / 实施 | 交付清单、环境、上线节奏 | 登记交付产品、核对交付与合同范围 |
| 授权运营 / License Ops | SN 策略、激活规则、BitAnswer 配置与异常处理 | 配置授权、处理回调与规则事件、与 SDK/回调服务协同 |
| 订单 / 运营支持(可与 CRM 合并) | 订单、SKU、开票与履约对齐 | 保证「卖什么」与「授什么」一致 |
| 研发 / 平台工程 | SDK、Webhook 服务、可观测性 | 保障集成稳定、排障与版本演进 |
| 管理层 / 财务合规 | 收入确认、审计、风险 | 审计轨迹与报表,非日常操作主用户 |
### 6.3 核心模块地图(与四大域 + 支撑域)
- **客户 / 项目**:客户档案、项目/阶段、干系人,与合同/交付关联。
- **合同**:主数据、标的(产品/模块/席位数/期限)、状态(草稿→生效→变更→终止)。
- **交付产品**:交付批次与清单、与合同行项/SKU 映射、交付完成标记。
- **授权**BitAnswer 集成配置、SN 分配与生命周期、激活记录、功能与产品配置对应(如 per-product JSON)、规则 Webhook 入站与处置状态。
- **支撑(必要)**:集成与运维(回调地址、配置发布、Webhook 健康度);事件与规则编排(回调→业务动作/工单);设备/资产治理(可选);审计与合规;可观测性(激活与回调指标)。
### 6.4 关键用户旅程
1. **合同签订 → SN 分配 → 交付 → 激活**:合同生效后生成授权需求 → License Ops 完成(或同步)比特侧 SN/许可包 → 交付标记已交付 → 客户端经 SDK 激活 → 平台记录结果与原因码。
2. **规则触发 → Webhook → 运营处置**:比特规则满足 → HTTPS 接收回调 → 落库并关联客户/合同/SN → 运营认领、续期/冻结/升级等,写回状态并通知。
3. **合同变更 → 授权差异对齐**:变更后对比「合同授权视图」与当前比特/缓存状态 → 待办清单 → 批量调整并审计。
4. **交付争议 / 对账举证**:拉取合同条款 + 交付记录 + SN 发放与激活时间线 + 回调摘要,一条链查到底。
5. **新产品接入**:平台注册 SKU/功能矩阵 → 维护授权 JSON → 联调 SDK 与测试环境比特 → 上线检查清单(回调可达、监控就绪)→ 商务可报价签约。
### 6.5 MVP 与分阶段路线图(P0 / P1 / P2)
| 阶段 | 范围与理由 |
|------|------------|
| **P0MVP** | 客户/项目、合同、交付产品最小主数据与关联;SN 分配与激活结果回写;Webhook 入站 + 事件列表 + 与客户/合同/SN 的基础关联(挂不上先保留原始载荷);SDK 配置来源与环境区分。**理由**:先跑通主链与排障、审计雏形。 |
| **P1** | 合同行项与授权/SKU 规则化映射、变更 diff;回调处置工作流(认领、状态机、通知);设备治理(若适用)与审计报表;可观测仪表盘。**理由**:降本增效、减人为错误。 |
| **P2** | 与 CRM/订单深度同步、审批流、多法人;规则/策略可配置编排;到期续费与异常聚类等。**理由**:客户与产品线规模化后的必然需求。 |
### 6.6 成功指标(KPI
- **产品**:主链覆盖率(新签合同在平台内完成合同→交付→授权记录);SN/激活/回调与客户或合同关联成功率;合同生效到「许可就绪」中位时长;配置模板复用率。
- **运营**:回调队列处理 SLA、激活成功率与 Top 失败原因、合同与授权不一致类工单占比、关键字段变更可追溯比例(目标趋近 100%)。
### 6.7 风险与待决策问题(干系人澄清)
- 主数据权威(CRM/电子签/本平台)与冲突合并策略。
- BitAnswer 控制台与平台内闭环的边界、是否双写及对账。
- Webhook 安全(签名/幂等/重放)、SLA 与灾备是否与商务承诺一致。
- 多产品 JSON 与客户端/SDK 发版节奏、最低 SDK 版本策略。
- 日志与 PII 留存、数据跨境(若涉及)。
- License Ops 编制与工单机制是否在 P0 明确。
- 管理层优先指标:交付周期 vs 授权类客诉下降,影响功能取舍。
---
## 7. 技术架构维度:分析与评估
### 7.1 系统语境(Context
| 参与者 | 角色 |
|--------|------|
| **客户商务与交付管理平台** | 客户、项目、合同、交付项、产品/SKU、SN/设备台账、授权策略与比特 ID 映射;**不提供** BitAnswer 运行时。 |
| **比特安索** | 许可模型、规则、SN/设备生命周期;**出站 Callback** 至创飞 HTTPS。 |
| **客户现场客户端** | **craftlabs-authorization-sdk**`BitAnswerProvider` → native+ JSON 配置(含 `bitanswer.url` 等)。 |
```mermaid
flowchart LR
subgraph CF["创飞平台"]
P["商务与交付域\n项目/合同/交付/SN"]
I["授权集成层\n映射/配置/事件编排"]
W["Webhook 接入\nCallback 接收"]
end
subgraph BA["比特安索"]
C["控制台 / 规则引擎"]
CL["授权云 / 集团服务"]
end
subgraph EDGE["客户现场"]
APP["现场客户端\nSDK + Native"]
end
P --> I
W --> I
I --> P
C -->|"规则 Callback\nHTTPS POST + Token"| W
APP -->|"Login/激活/特征/心跳"| CL
CL -.->|"策略与许可结果"| APP
I -->|"配置与 SN 交付\n(流程+工件)"| EDGE
```
**要点**Callback 为 **比特 → 创飞**;客户端为 **现场 → 比特**;主数据与状态机在创飞平台闭环;**Webhook 须独立服务**(与本仓库 SDK 分离)。
### 7.2 逻辑架构:建议服务与模块
| 逻辑组件 | 职责 |
|----------|------|
| **商务与交付核心 API** | 客户、项目、合同、交付项、产品/SKU、SN/设备 CRUD 与状态机;为授权事件提供业务锚点。 |
| **授权集成服务** | 产品线 ↔ 比特产品/模版/业务/特征 ID;`craftlabs-auth-config` 生成/校验;多环境 `bitanswer.url`。可先 Git + Schema CI,再演进配置 API。 |
| **Webhook Ingress** | 公网 HTTPS、Token 鉴权、快速 `2xx`;置于网关/WAF 后;与核心库解耦,异步投递下游。 |
| **事件与工作流编排** | 按 `event` 路由;幂等键;补偿与工单;建议 MQ + DLQ。 |
| **配置与密钥分发** | 脱敏配置模板、环境参数、轮换策略进入发布流水线或配置中心;禁止密钥进镜像。 |
| **可观测与支持** | trace、脱敏审计日志、客户端错误分类上报。 |
### 7.3 数据域模型(高层)
```text
客户 ──* 项目
客户 ──* 合同
合同 ──* 交付项
交付项 *──* 产品/SKU
产品/SKU ──* 授权策略映射(比特 product/template/business/featureIds、策略版本、环境)
合同/交付项 ──* SN
SN *──* 设备(mid、换机历史)
授权事件流水(Callback + 可选客户端上报摘要)── 关联 SN、设备、合同/交付项、event、幂等键、处理状态
```
**要点**:SN 是连接比特控制台与创飞商务数据的键;`LicensePolicyMapping` 将销售 SKU 映射为比特特征与规则;设备域对齐 `device:*` 类事件。
### 7.4 集成模式摘要
- **规则 Callback**Ingress 尽快 `2xx`;校验 `x-bitanswer-token` 并支持轮换;幂等键与乱序容忍(按状态机合法性判定)。
- **边缘配置**:符合 Schema 的 JSON、环境 `bitanswer.url`SN 与 `applicationData` 经安全通道交付,与镜像构建分离。
- **密钥**KMS/Secret Manager;日志脱敏(token 不落库、不落日志)。
### 7.5 非功能需求(NFR)与技术风险
**NFR**HTTPS 全链路、WAF/限流、Webhook 多实例无状态、队列多 AZ、RTO/RPO 与降级;结构化日志与按 `event` 的指标;比特 SDK/集团服务 **兼容矩阵** 与发版联动。
**风险与缓解**Callback 洪峰 → MQ 与限流;幂等缺失 → 唯一约束/幂等表;SN 与合同不一致 → `sn:pre_activate` 校验 + 对账任务;SDK 升级破坏现场 → 预发演练与灰度、保留稳定 native 包。
### 7.6 分阶段技术交付(与 PM 阶段对齐)
| PM 阶段 | 技术验收要点 |
|---------|----------------|
| 需求与方案 | 语境图与逻辑架构评审;数据域 v1;事件清单与幂等策略;安全与密钥方案。 |
| 设计 | API/事件契约;Ingress 与队列拓扑;`LicensePolicyMapping` 模型与 Schema 对齐。 |
| 迭代 1(MVP | 联调环境 SDK 跑通;Webhook 最小实现(鉴权 + 入队 + 2xx);至少一类核心事件(如 `sn:post_activate`)打通台账。 |
| 迭代 2 | 设备域与 `device:*``pre_activate` 风控;DLQ 与补偿;配置 CI 校验。 |
| UAT / 上线 | 全事件抽样、异常与换机场景;监控告警;token 轮换与回滚手册。 |
---
## 8. 前端与后端实现维度:Spring Boot 4.0.\* + Vue 分阶段开发评估
### 8.1 技术栈与版本假设
| 层级 | 选型 | 说明 |
|------|------|------|
| 后端 | Java + **Spring Boot 4.0.\*** | **主版本线锁定 4.0**,采用官方 **4.0.x** 补丁(GA,非里程碑);**Jakarta** 命名空间;**Java 基线以 Boot 4 官方 System Requirements 为准**(当前一代为 **Java 17+**,生产 JDK 建议与团队统一为 **LTS,如 21**)。**选型理由**:4.0 线为后续 **AI 能力扩展**(Spring AI、工具调用、向量检索与观测组件等)预留同代生态位,避免在 3.x 与 4.x 之间二次迁移。 |
| 前端 | **Vue 3**Composition API+ **Vite** | Vue 3.x + Vite 稳定版;**锁版本**lockfile)避免构建漂移。 |
**AI 扩展(规划约束,非 MVP 必做)**:在 Spring Boot **4.0.\*** 上按需引入 **Spring AI**(或官方推荐组合)、统一 **Observability**Micrometer / OTel)与 **API 契约**springdoc),使对话、RAG、MCP/工具类集成与现有 REST、安全、配置模型一致;具体模块与版本在立项时按 Spring 官方 BOM 与兼容性矩阵选定。
### 8.2 三档总览(最小版 → 完整版)
| 档位 | 业务目标(一句话) | 前端重心 | 后端重心 | 风险量级 |
|------|-------------------|----------|----------|----------|
| **MVP** | 内部可用:主数据 + 合同/交付最小闭环 + 许可可查 + Callback 不断链 | 少页面、列表+表单、基础鉴权 | 粗粒度模块、REST 为主、Webhook 打通一条链、基础审计 | 中 |
| **Mid** | 流程与协作:状态机、报表雏形、异步与队列可观测 | 复杂表格/表单、按钮级权限、简单仪表盘 | Ingress 与 Worker 分离趋势、Outbox/MQ、幂等固化、OpenAPI 稳定 | 中高 |
| **Full** | 可运营、可审计、可扩展:治理、对账、多集成 | i18n/a11y、高级报表、动态权限/租户 | 限界上下文清晰、读模型/报表分离、SLO、审计独立管道 | 高 |
### 8.3 MVP(最小版本)
**目标**:客户/项目、合同、交付项 CRUD;SN 录入/绑定;Webhook 接收 → 校验 → 入队或直写 + **幂等**;关键写操作审计。
**前端(Vue**:布局 + 客户/项目/合同/交付/SN 列表与详情编辑;Callback 投递记录只读页;Pinia + 路由守卫 + axios 封装;表格分页排序;可选导出 CSV;少量单测 + 1~2 条 E2E。
**后端(Spring**:分包域:`identity``customer-project``contract``delivery``licensing``webhook-ingest``audit`REST `/api/v1/...`**Spring Security**JWT 或 Session+ 粗角色;**PostgreSQL 15** + **MyBatis-Plus** + Flyway/LiquibaseWebhook **inbox 或 MQ**;幂等键 `(source, message_id)``event_id`**springdoc-openapi**Micrometer + 结构化日志 + `/actuator/health`
**前后端依赖**:认证契约、分页/错误码、许可与合同关联模型、Webhook **schemaVersion** 约定。
**本档风险**:比特字段枚举与内部模型不一致;Webhook ACK 与落库事务边界;权限过粗导致 Mid 期大改。
**工作量(定性)****ML**(联调与 Webhook 占大头)。
### 8.4 Mid(增强版)
**目标**:合同/交付状态机与操作轨迹;许可映射与批量 SN;**MQ 为主路径**;失败重试与 Ops 入口;基础监控指标。
**前端**:状态时间轴;Webhook 失败列表与详情(脱敏);映射配置页;简单 KPI 卡片;可编辑表格、动态表单;**按钮级权限**;ECharts 可选;OpenAPI 客户端进 CI`Idempotency-Key` 与后端对齐。
**后端**Ingress **独立部署** 倾向;**Outbox** + DLQ**@PreAuthorize** 细粒度;乐观锁;领域事件;**OpenAPI v1 冻结**;分布式 Trace + 业务指标(Webhook lag、激活成功率)。
**风险**:异步与 UI「已受理」反馈不一致;报表与 OLTP 同库压力;映射与比特控制台不同步。
**工作量(定性)****L**。
### 8.5 Full(完整版)
**目标**:数据范围/多租户(若需要)、审计与合规导出、SLA 看板、对账与异常工单、配置模板化、BitAnswer 全生命周期与对账任务。
**前端**:模板/字典/集成配置后台;多维报表与订阅;审计检索;任务中心(长耗时对账);字段级权限与动态路由;虚拟滚动 + 游标分页;i18n 与 WCAG 目标;契约测试与性能脚本。
**后端**:模块化单体或拆服务;**CQRS 轻量**读模型;OAuth2/OIDC/SSOABAC/数据范围;读写分离或报表库;事件版本化与回放;OpenAPI 多受众与兼容 CI;SLO 与审计独立存储。
**风险**:拆分过早/过晚;审计写放大;业务规则与比特长期分叉(需防腐层与定期对齐)。
**工作量(定性)****LXL**。
### 8.6 横切能力递进
| 能力 | MVP | Mid | Full |
|------|-----|-----|------|
| OpenAPI | 开发态 | CI + v1 冻结 | 多受众 + 兼容检查 |
| 安全 | JWT/Session + 粗角色 | 细权限 + ingress 加固 | SSO + 数据范围 + 密钥轮换 |
| 异步 | 可选 MQ | Outbox + DLQ | 事件版本 + 回放 |
| 观测 | 日志 + health | Metrics + Trace | SLO + 审计管道 |
| 测试 | 手工为主 | E2E + 核心单测 | 契约 + 性能 + a11y |
---
## 9. 产品使用维度:MVP / Mid / Full 分阶段评估
> 本节从 **谁在用、要完成什么、界面呈现什么事实、如何运营** 描述,不展开 Spring/Vue 实现细节(见 §8)。
### 9.1 三档定位
| 档位 | 一句话目标 |
|------|------------|
| **MVP** | **卖—授—激活** 闭环可追溯,Callback 不丢,SN 可关联合同 |
| **Mid** | 规模化:**设备/换机、队列与补偿、对账与报表、通知** |
| **Full** | **变更治理、财务/审计级追溯、深度集成** 与多模式许可运营 |
### 9.2 MVP
**角色与任务**:商务建客户/项目/合同;交付登记交付物;License Ops 管 SN 与激活核对;订单支持维护合同行与 SKU 对应;研发保障 Callback 可查。
**必备界面**:客户档案;项目工作台;合同台账;合同行/订单行;交付登记;**SN/授权台账**;**Callback 收件箱**;比特侧状态摘要(只读/链接,不替代控制台)。
**用户要看到的「事实」**:客户与项目;合同标的;交付是否完成;SN 绑谁、状态;Callback 是否到达与处理结果;**客户→项目→合同→交付→SN** 端到端链路。
**运营流**:Callback 入站落库与状态更新或待办;SN 发放与激活核对;失败可重试或人工跟进。
**不做**:完整 CRM/电子签、精细设备治理、财务闭环、替代比特控制台全能力、客户自助门户。
**验收**:一条真实或模拟链路跑通;合法 Callback 100% 可检索;SN 可追溯到合同;权限与审计字段满足;无「孤儿 SN」至少可警告/筛选。
**依赖**License Ops、比特控制台规则与 token、现场交付节点、研发 Callback 端点。
### 9.3 Mid
**增量角色与任务**:交付管设备/场站与换机;Ops 队列 SLA 与批量 SN;客服按 SN 诊断;管理层看健康度。
**增量界面**:设备台账;换机/重激活;Callback 运营台(过滤、批量处理、死信);**对账视图**(标的 vs 已发 vs 已激活);待办/通知;产品线与环境映射只读或受控编辑。
**增量事实**:多设备历史;Callback 耗时与 TOP 原因;履约缺口;即将到期列表。
**不做**:完整 ERP 收入确认、全功能客户自助、经销商分润、自建对等规则引擎。
**验收**:换机场景台账完整;对账抽样一致;峰值 Callback 不丢;至少一类通知通道可测。
### 9.4 Full
**增量重点**:财务/合规审计与导出;管理层经营视图;产品/架构的变更与版本治理。
**增量界面**:合同变更与版本;授权策略库(映射版本、生效区间);客户健康度/续费;审计导出;可选客户只读门户;CRM/ERP 同步状态。
**验收**:变更闭环 T+X 与控制台一致;审计抽样可追溯;集成成功率与失败清单可处理。
### 9.5 与 P0 / P1 / P2 路线对齐
| 路线 | 含义 | 与本三档 |
|------|------|----------|
| **P0** | 能接单、交付、授权,Callback 不断链 | ≈ **MVP** 主体 |
| **P1** | 运营效率:队列、对账、设备、通知 | ≈ **Mid** |
| **P2** | 治理与增长:变更、经营分析、深度集成 | ≈ **Full** |
P0/P1/P2 为**优先级语言**MVP/Mid/Full 为**产品包边界**;排期可交错,但**验收边界**建议按档固化,避免 MVP 膨胀。
---
## 10. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:基于工作区走查与比特文档推导创飞侧平台与交付物。 |
| 2026-04-06 | 增补:平台正式命名;§6 产品维度、§7 技术架构双 Task 分析并入本文。 |
| 2026-04-06 | 增补:§8 前端+后端(Spring Boot GA + Vue 3 + Vite)分档;§9 产品使用分档;与 P0–P2 对齐说明。 |
| 2026-04-06 | §8 后端基线明确为 **Spring Boot 4.0.\*** 长期线,并补充与 **AI 扩展**(Spring AI 等)同代演进说明。 |
+359
View File
@@ -0,0 +1,359 @@
# 客户商务与交付管理平台 — 业务流程设计与版本迭代排期
> **文档角色**:资深产品经理视角的 **全业务 BPM 走查**、**版本迭代计划** 与 **排期框架**(可与研发 Sprint 对齐后填入具体日历)。
> **计划基准日**2026-04-06(相对排期以 **T0** 表示立项/开工周)。
> **关联文档**[功能模块与功能点](chuangfei-platform-product-modules.md) · [平台与比特对接总览](chuangfei-bitanswer-integration-platform.md) · [回调与地址评估](bitanswer-callbacks-endpoints-assessment.md) · [授权制度与规则](bitanswer-licensing-design-and-rules.md) · [工作区工程划分](engineering/WORKSPACE_ENGINEERING_LAYOUT.md)
---
## 1. 工作区文档走查结论(摘要)
| 文档 | 已覆盖内容 | 本文补足内容 |
| -------------------------------------------------- | ----------------------------------------------------- | -------------------------------------- |
| `chuangfei-platform-product-modules.md` | M1M11 模块、功能点 ID、P0/P1/P2、角色权限矩阵 | **跨模块泳道流程**、**状态机与异常分支**、**发布列车与周次排期** |
| `chuangfei-bitanswer-integration-platform.md` | 对接边界、技术架构、Spring Boot 4.0. + Vue 分档、产品使用 MVP/Mid/Full | 将上述档位 **映射为可发布版本号与迭代切片** |
| `bitanswer-licensing-design-and-rules.md` | 产品/模版/业务/授权、特征项、规则 Callback 事件 | Callback 在 BPM 中的 **触发点与系统边界** |
| `bitanswer-callbacks-endpoints-assessment.md` | URL 类型、Webhook 与 SDK 分工 | 在排期中固定 **Webhook 工程与联调节点** |
| `craftlabs-auth-config` Schema / `examples/config` | 客户端配置形态 | 在 **BP-10****V1.0** 中体现配置发布依赖 |
**排期假设(可调整)**:每迭代 **2 周**;团队为 **1 个全栈小队**(可并行时压缩总周数);**UAT 每大版本末预留 1 迭代**。
---
## 2. 业务流程总览索引
| 流程编号 | 流程名称 | 主价值 | 涉及模块 | 主责角色 |
| --------- | ------------------- | ---------- | ----------- | ----------- |
| **BP-01** | 客户与项目准入 | 主数据干净、可挂合同 | M1、M11 | 商务、管理员 |
| **BP-02** | 合同起草、审批与生效 | 「卖什么」入台账 | M2、M10 | 商务、订单支持 |
| **BP-03** | 交付计划与交付完成 | 「可激活」前置 | M3、M2 | 交付 |
| **BP-04** | 授权需求与 SN 发放 | 合同→许可载体 | M4、M2、M3、M6 | License Ops |
| **BP-05** | 现场激活与状态回写 | 闭环到台账 | M4、比特、现场客户端 | 交付、Ops |
| **BP-06** | 许可事件(Callback)入站与处置 | 不断链、可审计 | M5、M4、M8 | Ops、研发支撑 |
| **BP-07** | 合同变更与授权差异对齐 | 卖授一致 | M2、M4、M6、M9 | 商务、Ops |
| **BP-08** | 设备登记、换机与重激活 | 终端合规 | M7、M5、M4 | 交付、Ops |
| **BP-09** | 对账、异常工单与举证 | 管理降险 | M9、M8、M10 | 管理层、财务、合规 |
| **BP-10** | 授权集成配置发布 | 客户端可连比特 | M6、研发 | 架构、Ops |
| **BP-11** | 账户开通、登录与权限变更 | 安全访问 | M11、M10 | 管理员、安全 |
---
## 3. 业务流程设计(详述)
### 3.1 BP-01 客户与项目准入
**目标**:任何合同必须挂在已存在且已校验的客户/项目下。
**步骤**:① 管理员维护字典(行业、区域等)→ ② 商务创建客户档案(必填项校验)→ ③ 创建项目并关联客户、干系人 → ④(可选)客户/项目冻结策略生效。
**输出物**:客户 ID、项目 ID;审计记录(M10-F01)。
**异常**:重复客户 → P2 合并流程(M1-F08);权限不足 → M11 拦截。
---
### 3.2 BP-02 合同起草、审批与生效
**目标**:合同状态合法迁移,行项可支撑后续交付与 SN 绑定。
**状态机(逻辑)**`草稿``待生效``生效``生效``变更中``生效(新版本)``生效``终止/到期`
**步骤**:① 选客户与项目 → ② 录入合同头与 **合同行项**(SKU/数量/期限)→ ③(P1)附件上传 → ④ 商务/订单支持核对订单号关联(M2-F06)→ ⑤ 状态置为生效。
**输出物**:合同 ID、行项 ID;变更时版本号(M2-F07)。
**异常**:行项为空或金额/数量口径冲突 → 阻断生效;越权编辑 → 审计告警。
---
### 3.3 BP-03 交付计划与交付完成
**目标**:交付批次与清单与 **合同行** 对齐,标记「已交付」作为 SN 发放门禁(可配置)。
**步骤**:① 基于项目/合同创建交付批次 → ② 维护交付清单(产品实例、环境说明 P1)→ ③ 关联合同行项 → ④ 交付工程师确认完成(M3-F05)→ ⑤ 状态变为已交付。
**输出物**:交付批次 ID、完成时间。
**异常**:未关联合同行的交付项 → 警告或阻断(系统参数 M11-F20)。
---
### 3.4 BP-04 授权需求与 SN 发放
**目标**:从「生效合同 +(建议)已交付」生成许可需求,SN 绑定合同行/项目/客户。
**步骤**:① License Ops 查看授权需求清单(M4-F08,可与合同行汇总)→ ② 在比特控制台完成 SN/许可包操作(系统外)→ ③ 平台 **录入或导入 SN**M4-F01)→ ④ **绑定** 合同行与客户/项目(M4-F02)→ ⑤ 状态:已发放 → 通知客户/交付(可手工,Mid 接 M8)。
**输出物**SN 记录、绑定关系、发放时间。
**异常**:孤儿 SN(无绑定)→ 警告列表;与合同数量超额 → 规则校验(P1 增强)。
---
### 3.5 BP-05 现场激活与状态回写
**目标**:客户端经 **craftlabs-authorization-sdk** 与比特交互后,平台侧可见激活结果。
**步骤**:① 交付将 SN 与环境说明交付客户 → ② 现场部署并配置 `bitanswer.url` 等(BP-10 已发布配置)→ ③ 客户端激活 → ④ Ops **回写** 激活成功/失败及原因码(M4-F05)或接口同步(若建设)→ ⑤ 更新 SN 状态。
**输出物**:激活时间、状态、备注。
**异常**:长期未激活 → Mid 纳入报表 M9-F02;失败原因字典维护(M11-F19)。
---
### 3.6 BP-06 许可事件(Callback)入站与处置
**目标**:比特规则触发后 **HTTPS Callback** 不丢、可关联、可关闭。
**步骤**:① Webhook Ingress 验签/token → 快速 2xx → ② 落库事件(M5-F01~F04)→ ③ 解析 `sn` 尝试关联 SN/合同 → ④ Ops 处置(认领、备注、更新业务状态或建待办 M8)→ ⑤ 失败进重试/DLQ(Mid)。
**事件类型对齐**(参见 [bitanswer-licensing-design-and-rules.md](./bitanswer-licensing-design-and-rules.md)):`sn:pre_activate` / `post_activate``device:pre_activate` / `post_activate``yunbaobao:session_logout` 等。
**异常**:无法解析关联 → 人工挂接;重复投递 → 幂等不重复改状态。
---
### 3.7 BP-07 合同变更与授权差异对齐
**目标**:合同变更后,**应授 vs 实授** 可对比、可出待办。
**步骤**:① 发起合同变更(M2-F07)→ ② 系统生成 **授权差异提示**(行项增减/延期)→ ③ Ops 在比特控制台调整并更新平台 SN/状态 → ④ 关闭变更单。
**输出物**:变更版本、操作记录(M10)。
**依赖**Mid 报表 M9、映射 M2-F08 / M6。
---
### 3.8 BP-08 设备登记、换机与重激活
**目标**`mid` 与 SN 关系清晰,换机与 `device:`* Callback 一致。
**步骤(Mid+**:① 登记设备(M7-F01)→ ② 绑定 SN 历史(M7-F02)→ ③ 换机申请与记录(M7-F03)→ ④ 重激活后 Callback 更新台账。
**异常**:终端数超限 → 提示对齐比特策略(不替代控制台规则)。
---
### 3.9 BP-09 对账、异常工单与举证
**目标**:管理层/财务/合规可拉通 **合同—交付—SN—激活—Callback**
**步骤(Mid+**:① 打开对账视图(M9-F01~F03)→ ② 导出(权限控制 M9-F04)→ ③ 异常项生成待办(M8)→ ④ 合规按 M10 导出审计包(Full)。
---
### 3.10 BP-10 授权集成配置发布
**目标**:产品线、环境、`bitanswer.url`、特征映射与 JSON 模板 **版本化发布** 到现场可安装包或配置中心。
**步骤**:① M6 维护映射与模板 → ② Schema 校验(可与 CI 联动)→ ③ 发布记录(M6-F06)→ ④ 随产品发版或运维下发。
**依赖**:本仓库 `[schemas/craftlabs-auth-config.schema.json](../schemas/craftlabs-auth-config.schema.json)`
---
### 3.11 BP-11 账户开通、登录与权限变更
**目标**:最小权限、会话可追溯、敏感操作可审计。
**步骤**:① 管理员创建用户并分配角色(M11-F14~F16)→ ② 用户登录/改密/登出(§12.1)→ ③ 角色变更写 M10/M11-F21。
**异常**:暴力破解 → 锁定(M11-F05);人员离职 → 禁用与会话失效(M11-F12)。
---
## 4. 端到端泳道流程(主链路)
```mermaid
flowchart TB
subgraph SALES[商务 / 订单支持]
A1[BP-01 客户/项目]
A2[BP-02 合同与行项]
end
subgraph DEL[交付]
B1[BP-03 交付完成]
end
subgraph OPS[License Ops]
C1[BP-04 SN 发放与绑定]
C2[BP-06 Callback 处置]
C3[BP-05 激活回写]
end
subgraph DEVOPS[架构 / 研发]
D1[BP-10 配置发布]
D2[Webhook 服务运维]
end
subgraph EDGE[客户现场 + 比特]
E1[客户端 SDK 激活]
E2[比特控制台 / 规则]
end
A1 --> A2 --> B1 --> C1
D1 --> E1
C1 --> E1
E1 --> E2
E2 -->|Callback| C2
C2 --> C3
B1 -.->|门禁| C1
```
---
## 5. 关键状态机汇总(产品规则)
| 对象 | 状态(节选) | 允许迁移(示例) |
| ----------- | ------------------------- | ----------------------- |
| 合同 | 草稿 / 待生效 / 生效 / 变更中 / 终止 | 草稿→待生效→生效;生效→变更中→生效(新版) |
| 交付批次 | 未交付 / 部分 / 已交付 | 未交付→已交付(或经部分) |
| SN | 未发放 / 已发放 / 已激活 / 冻结 / 异常 | 未发放→已发放→已激活;任意→冻结(授权) |
| Callback 事件 | 待处理 / 已处理 / 失败 / 忽略 | 待处理→已处理或失败→(重试)已处理 |
| 用户账号 | 启用 / 禁用 | 启用↔禁用;禁用强制会话失效 |
详细字段级规则在 PRD/配置表(M11-F20)中展开。
---
## 6. 版本迭代总体规划(Roadmap)
| 版本 | 定位 | 对应产品包 | 目标上线(相对 T0) | 核心验收 |
| -------- | ------- | ------ | ---------------- | ---------------------------- |
| **V1.0** | MVP 可投产 | MVP | **T0 + 1012 周** | 主链路 BP-0106、11 + 一条真实项目 UAT |
| **V1.1** | MVP 加固 | MVP+ | T0 + 1416 周 | 性能、权限细项、生产监控 |
| **V1.5** | Mid 第一波 | Mid 核心 | T0 + 1822 周 | M7、M8、M9 主干 + BP-08 雏形 |
| **V1.6** | Mid 完成 | Mid | T0 + 2428 周 | 对账报表、Callback 运营台、SSO |
| **V2.0** | Full 基线 | Full | T0 + 3442 周 | 变更治理、审计导出、MFA/数据范围等 P2 |
**说明**:若 **双迭代并行**(后端 Webhook 与业务 API 分队),V1.0 可压至 **810 周**,但需明确接口契约负责人。
---
## 7. 分迭代排期(Release Backlog
> **三轨并行执行包**(后端双 JAR + 前端 Vue + 本仓 SDK):见 [engineering/PARALLEL_ITERATION_INDEX.md](./engineering/PARALLEL_ITERATION_INDEX.md) 与 [tracks/01](./engineering/tracks/01-backend-platform-webhook.md)、[02](./engineering/tracks/02-frontend-platform-ui.md)、[03](./engineering/tracks/03-client-sdk.md)。
### 7.1 V1.0 MVP(建议 56 个迭代 × 2 周)
| 迭代 | 周期(相对) | 目标产出 | 覆盖流程/模块 |
| ------ | --------- | ------------------------------------------------ | ----------------------- |
| **I1** | T0T0+2W | 工程脚手架、CI、环境;M11 登录/登出/会话/审计登录;用户角色粗矩阵 | BP-11 |
| **I2** | +2W+4W | M1 客户/项目全 P0;M11 字典、用户角色分配 | BP-01 |
| **I3** | +4W+6W | M2 合同与行项 P0 + 状态机;M10-F01 | BP-02 |
| **I4** | +6W+8W | M3 交付 P0M4 SN P0(录入/绑定/状态/回写) | BP-03、BP-04、BP-05(手工回写) |
| **I5** | +8W+10W | M5 Callback 收件箱 P0M6 环境与产品线 P0**Webhook 服务联调** | BP-06、BP-10 最小 |
| **I6** | +10W+12W | E2E 缺陷收敛;**UAT**;操作手册 v1;生产配置检查清单 | BP-0106、11 全链路 |
**V1.0 必须验收用例**:任选 1 条真实或准生产项目,完成 **客户→合同→交付→SN→(模拟)激活→Callback 可见→台账一致**
---
### 7.2 V1.1 加固(建议 2 迭代)
| 迭代 | 目标 |
| ------ | --------------------------- |
| **I7** | 权限码拆到按钮级;导出与脱敏;接口限流与安全头 |
| **I8** | 生产观测仪表盘;备份恢复演练;SN/合同批量导入稳定性 |
---
### 7.3 V1.5V1.6 Mid(建议 45 迭代)
| 迭代 | 目标产出 | 覆盖 |
| ----------- | -------------------------------------- | ----------------- |
| **I9** | M7 设备与换机 P1;M5 失败标注与重试入口 | BP-08 |
| **I10** | M8 待办 + 一种通知通道 | BP-06、BP-09 |
| **I11** | M9 对账与 Callback 统计 + 导出 | BP-09 |
| **I12** | M11 SSO、并发会话、强制下线、密码重置;M2/M4/M6 P1 增强项 | BP-02、BP-04、BP-10 |
| **I13(可选)** | M2 变更版本与差异提示;UAT Mid | BP-07 |
---
### 7.4 V2.0 Full(建议 57 迭代)
| 主题 | 包含功能域 |
| ------ | ----------------------------------- |
| 合规与审计 | M10-F03/F04;举证包;留存策略 |
| 安全 | M11 MFA、SECURITY_ADMIN、数据范围 M11-F17 |
| 集成与规模化 | M1 CRM 同步 P2;M6 影响分析;M9 订阅与经营看板 |
| 体验 | M8 模板与静默规则;可选客户只读门户(单独立项时移出) |
---
## 8. 功能点 × 版本映射矩阵(摘要)
> 完整清单见 [chuangfei-platform-product-modules.md](./chuangfei-platform-product-modules.md) 各表 **优先级** 列;下表按 **版本** 聚合。
| 版本 | P0 全量 | P1 主要增量 | P2 主要增量 |
| ------------- | --------------------------------------------------- | ------------------------------ | ----------------------------- |
| **V1.0** | 各模块标注 P0 的功能点 + M11 §12.1 F01F07、§12.2 F14F16、F19 | 不强制 | 不含 |
| **V1.1** | — | 稳定性与权限细化、批量导入 | — |
| **V1.5V1.6** | — | 全部模块 P1 为主(M7、M8、M9、M11 SSO 等) | 少量 P2 可提前(按商务压力) |
| **V2.0** | — | 收尾 Mid 缺口 | P2:MFA、安全管理员、数据范围、CRM、M10 导出等 |
---
## 9. 里程碑与依赖
| 里程碑 | 时间点(相对) | 依赖 |
| ------------------ | --------- | -------------------------- |
| M0 立项与范围冻结 | T0 | 本 Roadmap + 模块文档签字 |
| M1 α:可登录 + 主数据 | T0+4W | 身份设计、测试环境 |
| M2 β:合同+交付+SN | T0+8W | 法务字段定稿 |
| M3 γ:Callback 联调通过 | T0+10W | **比特控制台规则 URL**、公网 Ingress |
| M4 UAT 完成 | T0+12W | 种子用户、脱敏数据 |
| M5 生产首上 | T0+1214W | 运维 SLA、密钥 KMS |
| Mid GA | T0+2428W | SSO IdP、通知渠道账号 |
| Full GA | T0+34~42W | 法务审计字段、财务口径 |
**外部强依赖**:比特安索控制台权限、Callback 出口网络、现场客户端发版窗口(与 BP-10 同步)。
---
## 10. 假设、风险与待办
| 类型 | 内容 |
| ------ | ---------------------------------------------------------------- |
| **假设** | 单团队 2 周迭代;需求变更每周冻结;比特 API/控制台能力不降级 |
| **风险** | Webhook 延期则 V1.0 砍为「仅收件箱+人工」,激活仍走 BP-05 |
| **风险** | 合同字段反复 → I3 膨胀;建议首期 **法务一次评审** |
| **待办** | 将本文迭代 **I1I6** 导入 Jira/飞书 **Epic/Story**;为每条 BP 指定 **流程 Owner** |
---
## 11. 修订记录
| 日期 | 说明 |
| ---------- | ---------------------------------------------- |
| 2026-04-06 | 初版:工作区文档走查;BP-01~11 流程;泳道图;V1.0~V2.0 路线图与迭代排期。 |
| 2026-04-06 | §7 增加指向三轨并行执行文档(后端/前端/SDK)。 |
+532
View File
@@ -0,0 +1,532 @@
# 客户商务与交付管理平台 — 功能模块划分与功能点(产品视角)
> **平台全称**:广州创飞人工智能技术有限公司客户商务与交付管理平台。
> **文档性质**:产品经理视角的 **模块划分** 与 **功能点清单**,用于需求评审、版本切片与验收对齐。
> **关联文档**:[平台与比特对接总览](chuangfei-bitanswer-integration-platform.md)(定位、架构、分阶段路线) · [**业务流程与版本排期**](chuangfei-platform-bpm-and-roadmap.md)BPM、迭代计划) · [工作区工程划分](engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
> **优先级约定****P0** = MVP 必含;**P1** = 增强运营效率;**P2** = 治理与规模化。同一功能可在多期迭代交付,表中标注为「首期目标优先级」。
> **实现状态约定**:**✅** = 已实现(I1~I9 迭代交付);**◐** = 部分实现(缺字段或功能不完整);**○** = 未实现(规划中);**—** = 不适用(依赖前置模块)。状态反映截至 2026-05-25 的实现情况。
---
## 1. 模块总览
| 序号 | 模块名称 | 一句话职责 | 典型主要用户 |
| --- | -------------------- | ---------------------------------- | ---------------- |
| M1 | **客户与项目中心** | 客户主数据、项目与干系人,作为合同与交付的上游 | 商务、客户经理 |
| M2 | **合同与履约行** | 合同全生命周期、标的与行项,对齐「卖什么」 | 商务、订单支持 |
| M3 | **交付管理** | 交付批次、交付清单与完成状态,衔接「可激活」前提 | 交付/实施 |
| M4 | **授权与许可运营** | SN 台账、与合同/交付绑定、激活状态、与比特侧映射 | License Ops |
| M5 | **许可事件(Callback)运营** | 比特规则 Webhook 入站记录、关联、处置与补偿 | License Ops、研发支撑 |
| M6 | **授权集成与配置** | 产品线↔比特 ID 映射、环境 URL、客户端授权 JSON 治理 | 架构、License Ops |
| M7 | **设备与终端治理** | 设备标识、换机与激活历史(与 `device:`* 事件对齐) | 交付、License Ops |
| M8 | **通知与待办** | 关键事件触达、任务队列与 SLA 提示 | 全员(按角色) |
| M9 | **报表与对账** | 履约与授权一致性视图、缺口与到期 | 管理层、Ops、财务(只读) |
| M10 | **审计与合规** | 关键操作留痕、导出与留存策略 | 合规、管理层 |
| M11 | **身份、访问与平台管理** | **登录/登出与会话**、用户组织、**角色权限**、字典与系统参数 | 全员登录;管理员维护账号与权限 |
```mermaid
flowchart TB
M1[客户与项目 M1]
M2[合同与履约行 M2]
M3[交付管理 M3]
M4[授权与许可运营 M4]
M5[许可事件 Callback M5]
M6[授权集成与配置 M6]
M7[设备与终端 M7]
M8[通知与待办 M8]
M9[报表与对账 M9]
M10[审计与合规 M10]
M11[身份与平台管理 M11]
M1 --> M2
M2 --> M3
M2 --> M4
M3 --> M4
M6 --> M4
M5 --> M4
M4 --> M7
M5 --> M8
M4 --> M9
M3 --> M9
M2 --> M9
M1 --> M10
M2 --> M10
M3 --> M10
M4 --> M10
M5 --> M10
M11 --> M1
```
---
## 2. M1 客户与项目中心
**定位**:统一客户与项目上下文,避免合同、交付、SN 挂在「无名客户」或重复档案上。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ------------- | -------------------------------------------- | --- | --- |
| M1-F01 | 客户档案创建/编辑 | 客户名称、统一社会信用代码/客户编码、行业、地址、开票信息等(字段以法务/财务为准裁剪) | P0 | ◐ 部分实现 — 仅 name + credit_code,缺行业/地址/开票信息 |
| M1-F02 | 客户列表与检索 | 多条件筛选、分页、关键字搜索 | P0 | ✅ |
| M1-F03 | 客户详情聚合视图 | 关联项目数、在履约合同数、在途 SN 数等摘要(只读统计) | P0 | ✅ — 后端 `/summary` + 前端详情页摘要卡片已实现 |
| M1-F04 | 项目创建/编辑 | 项目名称、所属客户、阶段、计划起止、项目经理 | P0 | ◐ 部分实现 — 仅 name + customer_id + phase,缺计划起止、项目经理 |
| M1-F05 | 项目列表与筛选 | 按客户、阶段、时间筛选 | P0 | ✅ |
| M1-F06 | 项目干系人 | 客户侧联系人、内部负责人、角色标签 | P0 | ◐ — 后端 CRUD 已实现,前端 UI 待补 |
| M1-F07 | 客户/项目冻结与解冻 | 禁止新业务挂载或仅允许只读(规则可配置) | P1 | ◐ — 后端 PATCH 端点已实现,前端 UI 待补 |
| M1-F08 | 客户合并与去重 | 疑似重复客户识别、合并流程与审计 | P2 | ○ |
| M1-F09 | 与外部 CRM 主数据同步 | 以外部 ID 关联、增量同步状态展示(不替代 CRM 全能力) | P2 | ○ |
---
## 3. M2 合同与履约行
**定位**:合同是「卖什么」的权威来源之一;履约行/合同行是 SN 与交付的锚点。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | --------------- | ------------------------------- | --- | --- |
| M2-F01 | 合同登记与编辑 | 合同编号、客户、关联项目、签订日、生效日、终止条件 | P0 | ✅ |
| M2-F02 | 合同状态机 | 草稿、待生效、生效、变更中、终止/到期等;非法跳转拦截 | P0 | ✅ 状态机含 DRAFT→PENDING_EFFECTIVE→EFFECTIVE→CHANGING→TERMINATED |
| M2-F03 | 合同标的摘要 | 产品/模块/数量/期限/席位等业务口径展示(可与行项汇总一致) | P0 | ✅ |
| M2-F04 | 合同行项(履约行) | 多行:SKU 或产品包、数量、单价(可选)、交付与授权口径 | P0 | ✅ |
| M2-F05 | 合同附件 | 上传扫描件/电子签输出(存储与权限受控) | P1 | ◐ — 后端 POST 端点已实现,前端上传 UI 待补 |
| M2-F06 | 合同与订单关联 | 外部订单号、内部订单记录 ID(若存在订单系统) | P1 | ○ |
| M2-F07 | 合同变更与版本 | 变更单、版本号、影响授权差异提示(与 M4 联动) | P1 | ◐ — 后端 changes/complete 端点已实现,前端 UI 待补 |
| M2-F08 | 合同行与授权 SKU 规则映射 | 行项默认映射到许可 SKU/特征包(与 M6 联动) | P1 | ○ |
| M2-F09 | 合同到期与续费提醒 | 基于生效/结束日期的列表与订阅(与 M8 联动) | P2 | ○ |
---
## 4. M3 交付管理
**定位**:记录「交了什么、何时可激活」,是 License Ops 发放 SN 的前置依据之一。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ----------- | ------------------------------- | --- | --- |
| M3-F01 | 交付批次创建 | 关联项目/合同,批次号、计划交付日 | P0 | ✅ |
| M3-F02 | 交付清单 | 交付物条目:产品实例、数量、环境说明、备注 | P0 | ✅ |
| M3-F03 | 交付与合同行关联 | 每条交付行可关联合同行项,支撑对账 | P0 | ✅ |
| M3-F04 | 交付状态 | 未交付、已交付、部分交付;关键状态变更留痕 | P0 | ✅ 状态含 PENDING→DELIVERED→CANCELLED |
| M3-F05 | 交付完成确认 | 责任人、完成时间、可选客户签收记录 | P0 | ✅ |
| M3-F06 | 现场环境信息 | 部署地址、网络要求、联系人(敏感字段权限控制) | P1 | ○ |
| M3-F07 | 交付与 SN 发放门禁 | 规则:仅「已交付」合同范围可生成/绑定 SN(可配置为强/弱) | P1 | ◐ — 后端 deliveryGateEnabled 参数+闸门检查已实现,前端 SystemParamsView 已对接后端 API |
| M3-F08 | 交付模板 | 按产品线预置交付清单模板 | P2 | ○ |
---
## 5. M4 授权与许可运营
**定位**:SN 与激活事实的台账中心;不替代比特控制台,但与比特状态 **摘要对齐、可追溯**
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | -------------- | ---------------------------- | --- | --- |
| M4-F01 | SN 手工录入/导入 | 单条新增、批量导入(模板校验、重复提示) | P0 | ◐ 手工录入已实现,批量导入 UI 待补(后端 POST /batch-import 已就绪) |
| M4-F02 | SN 与合同/项目/客户绑定 | 必选关联路径之一(合同行或项目),禁止裸 SN 或仅警告 | P0 | ✅ |
| M4-F03 | SN 生命周期状态 | 未发放、已发放、已激活、已冻结、已回收、异常等 | P0 | ✅ 状态含 REGISTERED→ISSUED→ACTIVATED→SUSPENDED→REVOKED |
| M4-F04 | SN 详情页 | 展示绑定关系、发放记录、激活时间、最近事件摘要 | P0 | ✅ |
| M4-F05 | 激活结果回写 | 人工录入或接口同步:成功/失败及原因码分类 | P0 | ◐ 支持手工状态更新,缺原因码分类 |
| M4-F06 | 比特控制台状态摘要 | 同一 SN 的关键字段摘要或控制台链接(只读,权限控制) | **P1**(原 P0,因依赖比特控制台对接未完成降级) | ○ |
| M4-F07 | 批量 SN 操作 | 批量绑定、批量状态变更(审批可选) | P1 | ◐ — 后端 batch-import 已实现,前端批量操作 UI 待补 |
| M4-F08 | 授权需求单 | 由合同/交付生成的「待发放 SN」清单,供 Ops 执行 | P1 | ○ |
| M4-F09 | 试用/正式/续期标签 | 与业务口径一致的标签,便于筛选与报表 | P1 | ○ |
| M4-F10 | SN 与设备关联视图 | 展示绑定 `mid` 列表与历史(依赖 M7) | P1 | — 依赖 M7 |
| M4-F11 | 授权策略生效视图 | 展示当前映射版本、环境(与 M6 联动) | P2 | ○ |
---
## 6. M5 许可事件(Callback)运营
**定位**:承接比特规则 **HTTPS Callback**,保证 **不断链、可关联、可处置**
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ----------- | -------------------------------------------------------------------------------------------------------------------- | --- | --- |
| M5-F01 | 事件收件箱列表 | 按时间、事件类型、`sn`、处理状态筛选 | P0 | ✅ 支持多维度筛选 |
| M5-F02 | 事件详情 | 展示解析后字段 + 脱敏后的原始 payload;关联 SN/合同 | P0 | ✅ 含 payload 脱敏预览 |
| M5-F03 | 处理状态 | 待处理、已处理、失败、忽略;处理人与时间 | P0 | ✅ 状态含 PENDING→PROCESSED/FAILED/IGNORED |
| M5-F04 | 关联解析失败兜底 | 无法关联主数据时保留事件并支持人工挂接 | P0 | ✅ 支持人工挂接 SN/项目/合同 |
| M5-F05 | 事件类型字典 | `sn:pre_activate``sn:post_activate``device:pre_activate``device:post_activate``yunbaobao:session_logout` 等展示名与说明 | P0 | ✅ |
| M5-F06 | 失败原因标注 | Ops 可选分类,便于报表 | P1 | ✅ — 前端失败原因下拉 + 后端 DTO 已实现 |
| M5-F07 | 批量重处理/重试入口 | 在业务允许范围内触发补偿(与后端幂等策略一致) | P1 | ✅ 单条 + 批量重试均已实现(I8 单条 + I10 批量端点) |
| M5-F08 | 死信与积压监控视图 | 队列深度、最久未处理 TOP(与可观测联动) | P1 | ◐ — 后端 GET /stats/backlog + 前端积压统计卡片已实现 |
| M5-F09 | 事件驱动待办 | 自动生成待办卡片(与 M8 联动) | P1 | — 依赖 M8 |
| M5-F10 | 模拟投递(仅测试环境) | 联调验收工具 | P2 | ◐ — 后端 POST /simulate 已实现,前端 UI 待补 |
---
## 7. M6 授权集成与配置
**定位**:把「产品线—比特产品/模版/业务/特征 ID—环境 URL」管起来,支撑客户端 JSON 与联调。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ----------------- | -------------------------------------------------------------- | --- | --- |
| M6-F01 | 产品线定义 | 产品线编码、名称、说明 | P0 | ✅ |
| M6-F02 | 环境维度 | 开发/测试/预发/生产及对应 `bitanswer.url` 登记 | P0 | ✅ 含 seed 数据(dev/prod |
| M6-F03 | 比特侧 ID 映射 | 产品、模版、业务 ID 与产品线+环境绑定(与控制台一致) | P1 | ✅ — 前后端均已实现(IntegrationIdMappingView |
| M6-F04 | 逻辑功能键 ↔ 特征项映射 | 对齐 `craftlabs-auth-config``features.*.bitanswerFeatureId` 等 | P1 | ✅ — 前后端均已实现(IntegrationFeatureMappingView |
| M6-F05 | 授权 JSON 模板管理 | 模板版本、变更说明、与 Schema 校验结果(可接 CI) | P1 | ◐ — 前后端 CRUD 已实现(IntegrationJsonTemplateView),Schema 校验未关联 UI |
| M6-F06 | 配置发布记录 | 谁、何时、发布了哪一版到哪一环境 | P1 | ○ |
| M6-F07 | 控制台链接与说明 | 规则 Callback URL、token 轮换登记(非密钥明文展示) | P1 | ○ |
| M6-F08 | SDK / native 版本矩阵 | 与现场客户端兼容范围说明 | P2 | ○ |
| M6-F09 | 变更影响分析 | 映射变更影响哪些在服 SN/合同(只读分析) | P2 | ○ |
---
## 8. M7 设备与终端治理
**定位**:支撑浮动、换机、终端限制类场景,与比特 `device:`* 事件及 `mid` 对齐。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ----------------- | ----------------------- | --- | --- |
| M7-F01 | 设备登记 | `mid`、别名、场站、关联客户/项目 | P1 | ◐ — 登记/列表已实现,字段覆盖待确认 |
| M7-F02 | 设备与 SN 绑定历史 | 时间线:首次激活、换机、解绑 | P1 | ◐ — 绑定时间线已实现,完整性待确认 |
| M7-F03 | 换机申请与处理记录 | 轻量审批可选;处理结果与备注 | P1 | ◐ — 后端 swap-request 端点已实现,审批流待补 |
| M7-F04 | 设备列表与检索 | 按 SN、客户、场站筛选 | P1 | ✅ |
| M7-F05 | 与 Callback 设备事件联动 | 从事件跳转设备详情 | P1 | ○ |
| M7-F06 | 终端数/并发策略展示 | 只读展示合同或比特策略摘要(不重复造规则引擎) | P2 | ○ |
---
## 9. M8 通知与待办
**定位**:把「该谁处理」说清楚,降低 Callback 与 SN 异常堆积。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ------------ | ------------------------------- | --- | --- |
| M8-F01 | 站内待办列表 | 按角色过滤:待处理 Callback、待发放 SN、待核对激活 | P1 | ◐ — 待办中心已上线,自动化待办生成待接入 |
| M8-F02 | 待办认领与完成 | 状态流转与备注 | P1 | ◐ — 状态流转已实现,备注功能待补 |
| M8-F03 | 邮件/企业微信等一种通道 | 关键事件必达一种(可配置订阅) | P1 | ◐ — 通知通道配置 UI 已上线,实际发送逻辑未接入 |
| M8-F04 | 通知模板 | 事件类型 → 模板变量 | P2 | ○ |
| M8-F05 | 静默规则 | 重复事件聚合、防骚扰 | P2 | ○ |
---
## 10. M9 报表与对账
**定位**:给管理层与 Ops **履约 vs 授权** 的一致性视图。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------ | ---------------- | ------------------- | --- | --- |
| M9-F01 | 合同标的 vs 已发 SN 视图 | 按合同/行项汇总应发、实发 | P1 | ◐ — ContractSnReportView 已上线,数据维度待确认 |
| M9-F02 | 已发 vs 已激活视图 | 未激活占比、超期未激活列表 | P1 | ○ |
| M9-F03 | Callback 统计 | 按类型、状态、时间段的成功率与耗时分布 | P1 | ◐ — CallbackStatsView 已上线 |
| M9-F04 | 导出 CSV/Excel | 权限与脱敏策略受控 | P1 | ◐ — 后端 GET /reports/export 已存在,前端导出按钮待补 |
| M9-F05 | 项目健康度看板 | 多项目并行时的红黄绿(规则可配置) | P2 | ◐ — ProjectHealthView 已上线,红黄绿规则可配置性待确认 |
| M9-F06 | 订阅报表 | 定期邮件推送 | P2 | ◐ — SubscriptionReportView 已上线,后端推送逻辑待确认 |
---
## 11. M10 审计与合规
**定位**:满足内审与客户抽样举证,**关键操作不可抵赖**。
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------- | -------- | --------------------------- | --- | --- |
| M10-F01 | 关键字段变更日志 | 客户、合同、SN 绑定、状态变更:旧值/新值/人/时间 | P0 | ✅ |
| M10-F02 | 审计检索 | 按对象 ID、用户、时间范围查询 | P1 | ◐ — AuditSearchView 已上线,筛选维度待确认 |
| M10-F03 | 导出审计包 | 范围可选(项目/合同/时间窗),水印与权限 | P2 | ○ |
| M10-F04 | 留存策略配置 | 与法务对齐的保留周期说明(技术实现另见架构) | P2 | ◐ — AuditRetentionView 已上线,配置生效性待确认 |
---
## 12. M11 身份、访问与平台管理
**定位**:**账户如何安全进入与退出系统**;进入后**能看能改什么**由角色与数据范围控制;以下为 **认证与会话**、**授权模型落地**、**平台配置** 三部分。
### 12.1 账户登录、登出与会话
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------- | ------------- | ----------------------------------------------- | --- | --- |
| M11-F01 | 登录页 | 账号(工号/邮箱/登录名可配置一种为主)+ 密码登录入口;错误提示不暴露用户是否存在(防枚举) | P0 | ✅ |
| M11-F02 | 登出 | 主动登出:清除服务端会话或作废令牌、前端清理本地凭证 | P0 | ✅ |
| M11-F03 | 登录态保持与超时 | **空闲超时**自动登出并提示;可选「记住本次会话」策略(与安全基线平衡,默认保守) | P0 | ◐ — 前端 idleTimer 已实现(从 systemParams 读取 sessionTimeoutMinutes),后端会话管理待补 |
| M11-F04 | 未登录访问拦截 | 访问受保护路由时跳转登录页,登录成功后回跳原目标 URL(或安全白名单内路径) | P0 | ✅ |
| M11-F05 | 登录失败与锁定 | 连续失败次数阈值触发**临时锁定**或验证码;解锁策略(时长/管理员解锁)可配置 | P0 | ○ |
| M11-F06 | 登录/登出审计 | 记录成功/失败登出、时间、来源 IP、客户端类型(脱敏与留存策略另定) | P0 | ✅ |
| M11-F07 | 密码修改 | 已登录用户修改本人密码;校验旧密码强度与新密码策略 | P0 | ✅ — Profile 页改密弹窗已实现 |
| M11-F08 | 密码重置 | 管理员重置密码或邮件/短信重置链接(通道选一种即可);重置后可选强制首次登录改密 | P1 | ◐ — 后端 `POST /admin/reset-password` 已实现(非空操作),前端管理 UI 待补 |
| M11-F09 | 企业 SSO / OIDC | 与企业身份源单点登录;登出可与 IdP **单点登出**联动(若 IdP 支持) | P1 | ○ |
| M11-F10 | 双因素认证 MFA | TOTP/短信/企业令牌等一种;可配置为全员或高敏角色必选 | P2 | ○ |
| M11-F11 | 并发会话策略 | 同一账号是否允许多端同时在线;超出策略时踢旧会话或拒绝新登录(可配置) | P1 | ○ |
| M11-F12 | 管理员强制下线 | 安全或人事场景下终止指定用户本会话或全会话 | P1 | ○ |
| M11-F13 | 服务时间窗提示(可选) | 维护窗口登录页公告 | P2 | ○ |
### 12.2 用户、角色与权限配置(管理侧)
| 功能点 ID | 功能点名称 | 说明 | 优先级 | 实现状态 |
| ------- | ---------------- | --------------------------------------------- | --- | --- |
| M11-F14 | 用户与账号生命周期 | 创建、启用/禁用、离职归档;与 SSO 时同步外部主键 | P0 | ◐ — 后端 CRUD + 前端管理页面已实现(`/admin/users`),SSO 同步未做 |
| M11-F15 | 角色定义与分配 | 预置角色(见 §13)+ 可选自定义角色;用户可挂多角色 | P0 | ◐ 仅实现 SYS_ADMIN/DEVELOPER/OPS 三角色,产品定义 10+ 角色待补齐 |
| M11-F16 | 功能权限(RBAC | 菜单、按钮、API 操作与 **§13 权限码** 对齐;支持按环境预览「某用户看见什么」 | P0 | ◐ 路由级 RBAC 已实现,按钮级权限码未落地 |
| M11-F17 | 数据范围(Data Scope) | 按事业部/区域/客户组限制列表可见行(与 M11-F18 二选一或组合) | P2 | ○ |
| M11-F18 | 数据属主/团队 | 如「仅本人负责客户」「本团队项目」(字段:负责人、协作人) | P1 | ○ |
| M11-F19 | 业务字典 | 合同类型、交付类型、SN 异常原因分类等 | P0 | ✅ |
| M11-F20 | 系统参数 | 「孤儿 SN」强校验、交付门禁、会话超时分钟数、密码策略等 | P1 | ✅ — SystemParamController + platform_system_param 表 + 前端对接后端 API 已实现 |
| M11-F21 | 管理员敏感操作留痕 | 改角色、改权限、强制下线、重置密码等单独记审计 | P1 | ○ |
> **说明**:原 M11-F01F06 已拆并至 **12.1 / 12.2**;实现时功能点 ID 以研发 backlog 为准,本文 ID 供需求追溯。
---
## 13. 角色与权限体系
### 13.1 模型说明
- **认证(Authentication)**:谁登录、会话是否有效 —— 对应 **§12.1**。
- **授权(Authorization**:登录后能做什么 —— **RBAC**(角色 → 权限码)为主,**数据范围**(事业部 / 团队 / 属主)为增强(P1/P2)。
- **权限码**:建议与前后端统一枚举,例如 `module:resource:action`(如 `contract:order:export`),便于接口与按钮同源校验。
### 13.2 预置角色定义
| 角色代码 | 角色名称 | 定位 | 典型职责 | 当前实现 |
| ---------------- | --------- | --------- | ---------------------------------------- | --- |
| `SYS_ADMIN` | 系统管理员 | 平台配置与账号治理 | 用户/角色/字典/系统参数;**不默认拥有业务全量数据**时可配置为「仅管理」 | ✅ |
| `DEVELOPER` | 研发/开发人员 | 技术研发与调试 | M1~M4/M6 业务 CRUD + 集成配置只读;**无** Callback 处置权限 | ✅ *注:产品定义中无此角色,为 MVP 简化引入,I10 起废弃,由 SALES 替代* |
| `OPS` | 运营人员 | 许可运营 | Callback 处置 + 集成配置只读;**无** 客户/项目/合同/交付/SN 写权限 | ✅ *注:产品定义中无此角色,为 MVP 简化引入,I10 起废弃,由 LICENSE_OPS 替代* |
| `SECURITY_ADMIN` | 安全管理员(可选) | 账号与登录安全 | 锁定策略、强制下线、审计检索;与 `SYS_ADMIN` 分离(职责分离,P2 | ○ |
| `SALES` | 商务经理 | 客户与签约侧 | 客户、项目、合同维护;发起交付与授权需求 | ✅ I10 已实现—替代原 DEVELOPER 角色 |
| `ORDER_SUPPORT` | 订单/运营支持 | 履约对齐 | 合同行与 SKU、订单号关联;协助商务核对「卖授一致」 | ○ *产品定义角色,仍在规划* |
| `DELIVERY` | 交付工程师 | 现场交付 | 交付批次与清单、环境信息、交付完成确认 | ✅ I10 已实现(售前演示账号 delivery/delivery |
| `LICENSE_OPS` | 授权运营 | 许可台账与比特协同 | SN 全生命周期、Callback 处置、与控制台操作配合 | ✅ I10 已实现—替代原 OPS 角色 |
| `DEV_SUPPORT` | 研发/集成支撑 | 技术排障 | Callback 技术字段、集成配置**只读或受限编辑**;无业务合同删除权 | ○ |
| `FINANCE_VIEW` | 财务只读 | 对账与收入支撑 | 报表与合同/SN **只读**;无改密权外的写权限 | ○ |
| `COMPLIANCE` | 合规/审计 | 抽查与导出 | 审计日志、导出包;业务数据多为 **只读** | ○ |
| `EXEC_VIEW` | 管理层只读 | 经营视图 | 报表与健康度看板 **只读** | ○ |
| `READONLY_ALL` | 业务只读(可选) | 跨模块浏览 | 全业务 **只读**,用于培训或二线;敏感字段仍脱敏 | ○ |
**多角色**:用户可同时拥有 `SALES` + `DELIVERY` 等,权限取**并集**;互斥规则(如 `SYS_ADMIN` 与业务高敏导出)由企业策略在实现时约束。
### 13.3 模块级权限矩阵(摘要)
约定:**—** 无访问;**R** 查看;**W** 新建与编辑;**D** 删除;**X** 导出;**A** 审批/处置类(认领、重试、状态变更);**M** 模块管理配置(映射、字典、系统参数)。
| 模块 | SYS_ADMIN | SALES | ORDER_SUPPORT | DELIVERY | LICENSE_OPS | DEV_SUPPORT | FINANCE_VIEW | COMPLIANCE | EXEC_VIEW |
| ----------- | --------- | ---------- | ------------- | -------- | ----------- | ----------- | ------------ | ---------- | --------- |
| M1 客户与项目 | M / RWD | RWD | R | R | R | R | R | R | R |
| M2 合同 | M / RWDX | RWDX | RWD | R | R | R | RX | RX | R |
| M3 交付 | M / RWD | R | R | RWD | R | R | R | R | R |
| M4 授权/SN | M / RWDA | R | R | R | RWDA | R | RX | R | R |
| M5 Callback | M / RA | — | — | — | RA | R | R | R | R |
| M6 集成配置 | M | — | — | — | RW | R(只读为主) | — | R | — |
| M7 设备 | M / RW | R | R | RW | RW | R | R | R | R |
| M8 待办 | M | R | R | R | R | R | R | R | R |
| M9 报表 | M | R | R | R | R | R | RX | RX | R |
| M10 审计 | M | — | — | — | — | R | R | RX | — |
| M11 身份与平台 | M(全量) | 仅 F07 本人改密 | 同左 | 同左 | 同左 | 同左 | 同左 | R(审计检索) | — |
**说明**
- `SYS_ADMIN` 列中 **M / RWD** 表示:至少能 **M**(用户角色字典参数);是否开放业务 **RWD** 由企业决定 —— 建议生产环境 **默认关闭** 业务写权限,仅保留 M11 管理面。
- `SECURITY_ADMIN`(若启用):建议拥有 M11 中 F05~F12、F21 及 M10 审计检索 **RX**,**不**默认拥有 M4 SN 发放。
- `DEV_SUPPORT`M6 建议 **仅 R**;若需改测试环境映射可走 **变更工单 + 临时提权**
- 细粒度需在实现阶段将矩阵展开为 **权限码清单**(每个菜单、按钮、导出、删除各一条)。
### 13.4 权限码命名建议(示例)
| 权限码 | 含义 |
| ------------------------------- | -------------- |
| `auth:session:logout` | 本人登出(全员) |
| `auth:user:password:change` | 本人改密 |
| `admin:user:manage` | 用户 CRUD |
| `admin:role:manage` | 角色与权限绑定 |
| `customer:project:rw` | M1 读写 |
| `contract:order:rw` | M2 合同读写 |
| `contract:order:export` | M2 导出 |
| `delivery:batch:rw` | M3 交付读写 |
| `license:sn:rw` | M4 SN 读写 |
| `license:sn:export` | M4 导出 |
| `license:callback:process` | M5 处置 Callback |
| `integration:config:rw` | M6 配置写 |
| `integration:config:read` | M6 只读 |
| `report:view` / `report:export` | M9 |
| `audit:search` / `audit:export` | M10 |
### 13.5 与版本包的关系(对应 §14)
- **MVPI1I9,已完成)**:§12.1 的 F01/F02/F04/F06 + §12.2 的 F14(基础)/F15(简化三角色)/F16(路由级)/F19;§13.2 **实际落地 `SYS_ADMIN` + `DEVELOPER` + `OPS`**(简化角色集,非产品定义全量);§13.3 矩阵为 **粗粒度模块级**。MVP 未覆盖的 P0 项(如 M1-F03/F06、M11-F03/F05/F07)标记为已知缺口,Mid 阶段补齐。
- **MidI10I13,待实现)**M7 设备 + M8 通知/待办 + M9 报表对账 + 补齐 MVP 遗留 P0 + M2/M4/M5/M6 P1 增强项 + SSO/并发会话/强制下线/密码重置 + 废弃 `DEVELOPER`/`OPS`,落地产品定义角色集。
- **FullV2.0,规划)**MFA、`SECURITY_ADMIN`、事业部数据范围、审计导出包、CRM 同步、细粒度互斥策略。
---
## 14. 按版本包的功能边界(与 P0 / P1 / P2 对齐)
| 版本包 | 状态 | 包含模块与要点 |
| -------- | --- | ---------- |
| **MVPI1I9** | ✅ **已完成** | M1/M2/M3/M4 核心功能 + M5 收件箱与处置 + M6 环境/产品线只读 + M10 审计日志 + **M11 JWT 登录/路由守卫/粗粒度三角色/字典**。角色矩阵为 **§13.3 粗粒度(简化三角色)**;自研许可证管理(V6)为额外交付。详见 §16 原型章节。 |
| **MidI10I13** | 🕐 **进行中** | MVP + M7 设备 + M8 通知待办 + M9 报表对账 + 补齐 MVP 未覆盖的 P0 项 + M2/M4/M5/M6 P1 增强 + M10-F02 审计检索 + **M11 SSO/并发/强制下线/密码重置/数据属主** + **权限码细拆** + **角色模型对标产品定义集**。 |
| **FullV2.0** | 📋 **规划中** | Mid + M2/M6/M9/M10/M11 的 P2 项 + M1/M8 集成与智能化增强 + **MFA、安全管理员、事业部数据范围、审计导出包、CRM 同步**。 |
---
## 15. 修订记录
| 日期 | 说明 |
| ---------- | ---------------------------------------------------------------------------------- |
| 2026-04-06 | 初版:产品视角模块划分与功能点表。 |
| 2026-04-06 | 增补:M11 扩展为身份/访问/平台管理;**登录/登出/会话**功能点;**§13 角色与权限体系**(预置角色、模块矩阵、权限码示例);版本包与 §14 对齐。 |
| 2026-05-25 | **全面更新**:所有功能点表增加「实现状态」列,标注 ✅/◐/○;M4-F06 降级至 P1;§13 角色表增加当前实现对照列;§14 版本包反映 I1~I9 完成状态;新增 **§16 原型实现说明**。 |
---
## 16. 原型实现说明(I1~I9 迭代交付)
> 本章记录 **2026-04 至 2026-05I1I9)** 已交付原型的具体范围,供产品验收、集成方评估与后续迭代规划使用。原型基于 **三轨并行**(后端双 JAR + 前端 Vue + 客户端 SDK)模式交付。
### 16.1 原型定位与范围
| 维度 | 说明 |
|------|------|
| **迭代范围** | I1(脚手架/M11)→ I9Webhook 出库状态只读),共 9 个迭代 |
| **原型目标** | 跑通 BP-01~06、11 主链路:客户→项目→合同→交付→SN→Callback→审计 |
| **交付形态** | 两枚 Fat JARdelivery-platform-api :8080 + license-webhook-ingress :8081+ Vue 3 SPA + Rust cdylib + Java SDK JAR |
| **部署方式** | Docker ComposePostgreSQL 15 + 双 JAR + Prometheus/Grafana 可选)或单机 `java -jar` |
| **覆盖模块** | M1M6 P0 核心功能 + M10-F01 + M11 基础身份与访问 + 自研许可证管理(V6 额外) |
### 16.2 前端原型(delivery-platform-ui
| 页面 | 路由 | 对应模块 | 实现说明 |
|------|------|---------|---------|
| 登录页 | `/login` | M11 | JWT Bearer 认证,演示账号 `admin/admin``ops/ops` |
| 首页 | `/` | — | 角色感知的模块快捷链接 + `GET /api/v1/ping` 调试 |
| 客户管理 | `/customers` | M1 | 列表/搜索/分页/新建/编辑对话框/删除(软删) |
| 项目管理 | `/projects` | M1 | 列表/按客户筛选/新建/编辑/删除(物理删) |
| 合同管理 | `/contracts` | M2 | 列表 + 三步创建向导 + 详情(状态机操作、行项管理、审计列表) |
| 交付管理 | `/deliveries` | M3 | 列表 + 新建向导 + 详情(抬头编辑、行项管理、状态变更) |
| 许可 SN | `/licenses/sn` | M4 | 列表 + 新建 + 详情(绑定/状态变更) |
| Callback 收件箱 | `/callbacks` | M5 | 列表(多维筛选)+ 详情(payload 脱敏预览、人工挂接、状态处置、DEAD 重放、出库状态) |
| 集成环境 | `/integration/environments` | M6 | 只读列表 |
| 产品线 | `/integration/product-lines` | M6 | 只读列表 |
| 403/404 | `/403` / `/*` | M11 | 路由级无权限/未找到提示 |
**技术栈**Vue 3 (Composition API) + Vite + Pinia + vue-router + axios + Element Plus。Token 存 `localStorage`(**已知安全缺陷**Mid 阶段应迁移至 HttpOnly Cookie)。
### 16.3 后端 API 原型
#### delivery-platform-api:8080
| Controller | 路由前缀 | 覆盖的模块 | 关键端点 |
|-----------|---------|-----------|---------|
| `AuthController` | `POST /api/v1/auth/login` | M11 | 登录(返回 JWT |
| `CustomerController` | `/api/v1/customers` | M1 | CRUD + 分页列表 + 软删 |
| `ProjectController` | `/api/v1/projects` | M1 | CRUD + 分页列表 + 物理删 |
| `ContractController` | `/api/v1/contracts` | M2 | CRUD + 行项管理 + 状态机 PATCH |
| `DeliveryBatchController` | `/api/v1/delivery-batches` | M3 | CRUD + 行项管理 + 状态 PATCH |
| `LicenseSnController` | `/api/v1/license-sns` | M4 | CRUD + 状态 PATCH |
| `LicenseController` | `/api/v1/licenses` | 自研许可 | License CRUD + 激活 + 过期(V6 额外) |
| `CallbackInboxController` | `/api/v1/callback-inbox` | M5 | 列表/详情/状态 PATCH/人工挂接/重放 Webhook 出库 |
| `IntegrationCatalogController` | `/api/v1/integration/*` | M6 | 产品线/环境只读列表与详情 |
| `PingController` | `/api/v1/ping` | — | 健康探测 |
| `CallbackInternalController` | `/internal/v1/callback-events` | M5 | Webhook→平台内部投递入口 |
**安全**JWT Bearer`PLATFORM_JWT_SECRET`+ 内部共享 Token`X-Platform-Internal-Token`+ 角色路由 `/api/v1/auth/login` `/actuator/health` 免认证。
#### license-webhook-ingress:8081
| Controller | 路径 | 职责 |
|-----------|------|------|
| `CallbackIngestController` | `POST /webhook/bitanswer/callback` | 比特 Callback 入站:验签(`x-bitanswer-token`)、幂等(`Idempotency-Key``externalMessageId`)、落收据表、入队投递 |
| `WebhookPlatformDeliveryOpsController` | `GET …/by-receipt/{receiptId}` | 出库状态只读查询 |
| `WebhookPlatformDeliveryOpsController` | `POST …/by-receipt/{receiptId}/replay` | DEAD 行重放 |
**安全**`CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` + `X-Webhook-Ops-Token`I8 起)。
### 16.4 Rust 核心库原型(native/craft-core
| 模块 | 文件 | 实现程度 |
|------|------|---------|
| **C ABI** | `lib.rs` | ✅ 8 个导出函数:`craft_initialize/activate/check_license/get_license_info/has_feature/release/heartbeat/destroy` |
| **Provider trait** | `trait_provider.rs` | ✅ 定义 Provider 接口:initialize/activate/check_license/heartbeat/has_feature/release/get_license_info/close |
| **自研 Provider** | `provider_selfhosted/` | ✅ 完整实现:activate + heartbeat + cache + license + protocol |
| **加密模块** | `crypto.rs` | ✅ AES-256-GCM 加解密、HKDF 密钥派生、RSA PKCS1v15 签名验证 |
| **设备指纹** | `device.rs` | ✅ 设备标识生成与校验 |
| **安全加固** | `security/` | ✅ 反调试(`anti_debug.rs`)、代码混淆(`obfuscation.rs`)、字符串加密(`string_encrypt.rs`)、完整性校验(`integrity.rs`)、动态 API 解析(`dynamic_api.rs` |
| **许可管理** | `license.rs` / `activate.rs` / `heartbeat.rs` / `session.rs` / `error.rs` | ✅ 核心许可状态、激活/心跳业务流程、错误码定义 |
| **公钥嵌入** | `build.rs` | ✅ 构建时嵌入 RSA 公钥 |
**已知局限**:仅实现 `SelfHostedProvider``BitAnswerProvider` 在 Rust 侧未实现。
### 16.5 Java SDK 原型(java/
| 模块 | 覆盖率 | 说明 |
|------|--------|------|
| `craftlabs-auth-core` | ✅ `AuthConfigs`(解析/校验/序列化)+ `AuthConfig`/`AuthConfigs` 配置模型 + `AuthProvider` 接口 + `AuthResult` + `LicenseInfo` + `NativeBridge`JNI 接口声明) |
| `craftlabs-auth-bitanswer` | ⚠️ `BitAnswerProvider` 实现 `AuthProvider` 接口但 **JNI 未对接真实原生库**,属于 Stub 状态 |
| `craftlabs-auth-selfhosted` | ⚠️ `SelfHostedAuthProvider` 基础实现,未对接 Rust 核心库(当前为独立 Java 实现) |
| `craftlabs-auth-tests` | ✅ Schema 校验测试 + BitAnswerProvider 基础测试 |
| `schemas/craftlabs-auth-config.schema.json` | ✅ 完整 JSON Schema Draft 2020-12,支持 3 种 scenario + 2 种 provider |
### 16.6 原型已知局限
以下为审计发现的 25 个问题中与原型直接相关的重大局限:
| 类别 | 问题 | 影响 | 计划迭代 |
|------|------|------|---------|
| **安全** | 前端 Token 存 `localStorage`(非 HttpOnly Cookie | XSS 窃取风险 | Mid |
| **安全** | Callback `raw_payload` 全字段明文落库 | 可能含 PII,未脱敏 | I10 |
| **SDK** | `BitAnswerProvider` 未对接真实原生库 | SDK 无法实际与比特安索通信 | Mid |
| **SDK** | Java SelfHostedProvider 未调用 Rust 核心 | 自研授权路径不通 | Mid |
| **角色** | 仅实现 3 个角色(产品定义 10+),存在 `DEVELOPER`/`OPS` 非标角色 | 角色模型与产品定义不匹配 | I12 |
| **M1** | 客户表缺行业/地址/开票信息、项目表缺计划起止/项目经理 | 字段覆盖不足 | I10 |
| **M4** | 比特控制台状态摘要未实现(原 P0) | 需跳转比特控制台查看 | P1/Mid |
| **M6** | 产品线→比特 ID 映射、特征映射、JSON 模板管理均未实现 | BP-10 配置发布流程不完整 | Mid |
| **M7M9** | 设备/通知/报表模块完全未开始 | Mid 核心范围 | I10I12 |
| **测试** | 无 Playwright E2E 测试 | 回归覆盖不足 | I10 |
| **基础设施** | 无消息队列,Webhook→API 走轮询 HTTP | 无削峰能力,高并发受限 | I11 |
### 16.7 从原型到产品化的演进路径
```mermaid
flowchart LR
subgraph MVP["MVP(已完成 I1I9"]
A["BP-0106+11 主链路<br/>M1M6 P0 + M10-F01 + M11 基础<br/>自研许可证 V6 额外"]
end
subgraph Mid["MidI10I13"]
B["补齐 M1/M4/M11 P0 缺口<br/>M7 设备 + M8 通知 + M9 报表<br/>M2/M5/M6 P1 增强<br/>SSO + 角色模型对齐<br/>BitAnswerProvider 对接"]
end
subgraph Full["FullV2.0"]
C["MFA / SECURITY_ADMIN<br/>数据范围 / 审计导出<br/>CRM 同步 / 变更治理<br/>消息队列 / 读模型分离"]
end
MVP --> Mid --> Full
```
**关键里程碑**
- **当前**:MVP 原型已完成,可支撑一条真实或准生产项目全链路 UAT
- **Mid(目标 T0+24~28 周)**:具备设备治理、通知协作、对账报表,角色模型对标产品定义
- **Full(目标 T0+34~42 周)**:合规审计、深度集成、规模化运营能力
@@ -0,0 +1,307 @@
# 交付平台前端 UI 需求规格说明(走查稿)
> 依据 `web/delivery-platform-ui` 源码整理,供 Figma Make / 设计迭代与产品跟进维护。
> 侧栏实现见 `src/layout/MainLayout.vue`;路由与角色见 `src/router/index.js`。
---
## 1. 全局壳层(需登录页共用)
| 区域 | 内容 |
|------|------|
| 侧栏品牌 | 文案:`创飞 · 交付平台` |
| 侧栏菜单 | 见 §2(按角色 `v-if` 显示) |
| 顶栏右侧 | 当前用户展示名(`displayName`)、链接按钮「退出」 |
| 主内容区 | 路由出口,背景为控制台风格浅灰 |
### 1.1 角色与菜单可见性
与路由 `meta.roles` 一致:
- **SYS_ADMIN**:可见下文全部侧栏项(无单独剔除)。
- **DEVELOPER**:客户管理、项目管理、合同管理、交付管理、许可 SN、集成环境、产品线;**无** Callback 收件箱。
- **OPS**Callback 收件箱、集成环境、产品线;**无** 客户 / 项目 / 合同 / 交付 / 许可 SN。
### 1.2 信息架构说明
当前侧栏为 **单层 `el-menu`**,无折叠分组、无物理「二级子菜单」。本文 **一级 = 产品模块**,**二级 = 侧栏入口 + 由其进入的子页面**(详情、向导、弹窗表单)。
---
## 2. 按模块:菜单项 → 页面 → 数据与操作
### 2.1 首页
| 项 | 说明 |
|----|------|
| 路由 | `/``HomeView.vue` |
| 侧栏 | 「首页」 |
| 内容 | 信息 AlertI7:按角色展示入口;Callback 仅 OPS / SYS_ADMIN);当前用户与角色;**与侧栏一致的模块快捷链接**(随角色过滤) |
| 附加 | 「调试」:`GET /api/v1/ping` 按钮 + JSON 文本结果区 |
---
### 2.2 客户管理
| 项 | 说明 |
|----|------|
| 路由 | `/customers``CustomersView.vue` |
| 侧栏 | 「客户管理」 |
| 列表筛选 | 关键词(名称或统一社会信用代码)、「查询」 |
| 表格列 | 客户名称、统一社会信用代码 |
| 行操作 | 编辑、删除(确认框) |
| 工具栏 | 「新建客户」 |
| 分页 | 10 / 20 / 50total、sizes、prev、pager、next、jumper |
#### 无独立菜单:新建 / 编辑客户(`el-dialog`,宽约 480px
| 字段 | 校验 / 约束 | API 语义 |
|------|----------------|----------|
| 客户名称 | 必填,`maxlength=200`,字数统计 | `POST` / `PUT` body |
| 统一社会信用代码 | 选填,`maxlength=32` | 空则不发该字段 |
| 底部 | 取消、保存(loading | `createCustomer` / `updateCustomer` |
---
### 2.3 项目管理
| 项 | 说明 |
|----|------|
| 路由 | `/projects``ProjectsView.vue` |
| 侧栏 | 「项目管理」 |
| 列表筛选 | 客户下拉(可清空、可搜索)、「查询」 |
| 表格列 | 客户(名)、项目名称、阶段(字典中文) |
| 行操作 | 编辑、删除(确认框) |
| 工具栏 | 「新建项目」 |
| 分页 | 同客户管理 |
#### 无独立菜单:新建 / 编辑项目(`el-dialog`,宽约 520px
| 字段 | 校验 / 约束 | 说明 |
|------|----------------|------|
| 客户 | 必填 | 选项来自客户列表(最多 500 条) |
| 项目名称 | 必填,`maxlength=200` | |
| 阶段 | 必填 | `GET /api/v1/dictionaries/PROJECT_PHASE`,前端兼容多种响应包裹形态 |
---
### 2.4 合同管理
| 项 | 说明 |
|----|------|
| 列表路由 | `/contracts``ContractsView.vue` |
| 侧栏 | 「合同管理」 |
| 列表筛选 | 关键词(合同标题)、「查询」 |
| 表格列 | 合同标题/编号、客户、项目、状态(Tag)、创建时间 |
| 行操作 | 「详情」→ `/contracts/:id` |
| 工具栏 | 「新建合同」→ `/contracts/new` |
#### 无菜单:新建合同向导(`/contracts/new``ContractWizardView.vue`
三步 `el-steps`
**步骤 0 — 客户与项目**
- 客户必选;项目必选;项目下拉随所选客户过滤;更换客户时清空项目。
**步骤 1 — 合同基本信息**
- 合同标题/编号:必填,`maxlength=256`,字数统计。
- 备注:选填,`textarea``maxlength=4000`
**步骤 2 — 明细行**
- 表内编辑:标的/行项名称(`itemName`)每行必填,`maxlength=256`
- 数量:`el-input-number``precision=4``min=0.0001`,须大于 0。
- 单位:选填,`maxlength=32`
- 至少保留一行;「添加明细」;「删除」(仅一行时删除禁用)。
**提交**:先 `createContract`,再对每行 `addLine`;成功跳转合同详情。
#### 无菜单:合同详情(`/contracts/:id``ContractDetailView.vue`
| 区块 | 行为 |
|------|------|
| 头部 | ← 合同列表;标题「合同详情」;状态 Tag;**草稿** 显示「保存」 |
| 描述区 | 标题/备注:草稿可编辑;客户/项目只读(优先接口 name,否则 ID 映射) |
| 状态操作条 | 依状态显示按钮(均需确认):草稿→提交待生效;待生效→确认生效;生效→发起变更 / 终止合同;变更中→完成变更 |
| 合同明细 | 草稿可「添加明细」、行内编辑/删除;非草稿只读 |
| 明细弹窗 | 添加/编辑:行项名称必填;数量必填;单位选填 |
| 最近审计 | 列:时间、操作者、动作、摘要(`listAuditEvents` 归一化展示) |
**合同状态枚举(Tag**
| 值 | 中文 |
|----|------|
| DRAFT | 草稿 |
| PENDING_EFFECTIVE | 待生效 |
| EFFECTIVE | 生效 |
| CHANGING | 变更中 |
| TERMINATED | 已终止 |
---
### 2.5 交付管理
| 项 | 说明 |
|----|------|
| 列表路由 | `/deliveries``DeliveriesView.vue` |
| 侧栏 | 「交付管理」 |
| 列表筛选 | 项目下拉、批次编码关键词、「查询」 |
| 表格列 | 批次编码、项目、合同 ID、状态 Tag、计划交付日、创建时间 |
| 行操作 | 「详情」→ `/deliveries/:id` |
| 工具栏 | 「新建交付批次」→ `/deliveries/new` |
#### 无菜单:新建交付批次(`/deliveries/new``DeliveryBatchWizardView.vue`
| 字段 | 校验 | 说明 |
|------|------|------|
| 项目 | 必填 | 可搜索 |
| 合同 | 选填 | 依赖项目;按 `projectId` 过滤合同 |
| 批次编码 | 必填,`maxlength=64` | |
| 计划交付日 | 选填 | 日期,`YYYY-MM-DD` |
| 备注 | 选填,`maxlength=4000` | |
| 底部 | 「创建并返回列表」/「创建并进入详情」 | |
#### 无菜单:交付批次详情(`/deliveries/:id``DeliveryBatchDetailView.vue`
| 区块 | 行为 |
|------|------|
| 头部 | ← 交付列表;状态 Tag;**PENDING** 时:「保存抬头」「标记已交付」(确认) |
| 抬头 | 批次编码 / 项目 / 合同 ID / 完成时间只读;**PENDING** 可编辑计划交付日、备注 |
| 交付明细 | **PENDING** 可增删改;列:排序、说明、数量、合同行 ID |
| 明细弹窗 | 排序 number 0999999;说明必填,`maxlength=512`;数量必填;合同行 ID 选填 |
**交付状态枚举**
| 值 | 中文 |
|----|------|
| PENDING | 待交付 |
| DELIVERED | 已交付 |
| CANCELLED | 已取消 |
---
### 2.6 许可 SN
| 项 | 说明 |
|----|------|
| 列表路由 | `/licenses/sn``LicenseSnListView.vue` |
| 侧栏 | 「许可 SN」 |
| 列表筛选 | 项目、SN 关键词、「查询」 |
| 表格列 | SN 编码、项目、合同行 ID、状态 Tag、创建时间 |
| 行操作 | 「详情」→ `/licenses/sn/:id` |
| 工具栏 | 「新建许可 SN」→ `/licenses/sn/new` |
#### 无菜单:新建许可 SN`/licenses/sn/new``LicenseSnWizardView.vue`
| 字段 | 校验 | 说明 |
|------|------|------|
| SN 编码 | 必填,`maxlength=128` | |
| 项目 | 选填 | 可搜索 |
| 合同行 ID | 选填,number ≥1 | MVP 手工录入提示 |
| 激活备注 | 选填,`textarea``maxlength=512` | |
| 底部 | 创建并返回列表 / 创建并进入详情 | |
#### 无菜单:许可 SN 详情(`/licenses/sn/:id``LicenseSnDetailView.vue`
| 区块 | 内容 |
|------|------|
| 基础 | SN 编码、创建时间 |
| 绑定与备注 | 表单:项目、合同行 ID、激活备注;「保存绑定」 |
| 状态 | 下拉:REGISTERED / ISSUED / ACTIVATED / SUSPENDED / REVOKED(含中英文标签);「更新状态」 |
**许可 SN 状态枚举**
| 值 | 中文 |
|----|------|
| REGISTERED | 已登记 |
| ISSUED | 已发放 |
| ACTIVATED | 已激活 |
| SUSPENDED | 已暂停 |
| REVOKED | 已吊销 |
---
### 2.7 Callback 收件箱(SYS_ADMIN、OPS
| 项 | 说明 |
|----|------|
| 列表路由 | `/callbacks``CallbackInboxView.vue` |
| 侧栏 | 「Callback 收件箱」 |
| 列表筛选 | 状态(PENDING / PROCESSED / FAILED / IGNORED)、事件类型、SN、项目 ID(文本)、「查询」 |
| 表格列 | 来源、外部消息 ID、事件类型、SN、状态 Tag、收件时间 |
| 行操作 | 「详情」→ `/callbacks/:id` |
> **扩展说明**`listCallbackInbox` API 另支持 `productLineId`、`environmentId`、`from`、`to` 等;当前 UI 未暴露,可在后续版本增加筛选。
#### 无菜单:Callback 详情(`/callbacks/:id``CallbackInboxDetailView.vue`
| 区块 | 规格 |
|------|------|
| 主信息 | ID、来源系统、外部消息 ID、事件类型、Schema 版本、幂等键、Webhook 收据 ID、SN、项目/合同/许可 SN/产品线/环境 ID、收件/处理时间、失败原因、备注 |
| Payload | 标题「Payload(脱敏预览)」;深色只读代码块,`pre`,约 `max-height: 420px` 可滚动 |
| I9 Webhook 出库状态 | **仅当**存在 `webhookReceiptId`:加载骨架 / 错误或描述列表:出库状态、尝试次数、上次错误、下次重试、出库更新时间 |
| I8 重新入队 | **同上条件**:配置说明(`LICENSE_WEBHOOK_BASE_URL``LICENSE_WEBHOOK_OPS_TOKEN`);按钮「重新入队出库(DEAD→待投递)」+ 确认 |
| 状态处置 | **仅 PENDING**:标已处理 / 标失败 / 忽略(均确认) |
| 人工挂接 | 许可 SN ID、项目 ID、合同 ID(文本输入);「保存挂接」需至少填一项 |
**Callback 状态枚举**
| 值 | 中文 |
|----|------|
| PENDING | 待处理 |
| PROCESSED | 已处理 |
| FAILED | 失败 |
| IGNORED | 忽略 |
---
### 2.8 集成环境
| 项 | 说明 |
|----|------|
| 路由 | `/integration/environments``IntegrationEnvironmentsView.vue` |
| 侧栏 | 「集成环境」 |
| 页面 | 只读表 + 「刷新」 |
| 列 | 编码、名称、类型、比特 URL(`bitanswerBaseUrl` 等兼容字段)、产品线 ID |
| 分页 | 默认 `pageSize=20`,可选 10/20/50 |
---
### 2.9 产品线
| 项 | 说明 |
|----|------|
| 路由 | `/integration/product-lines``IntegrationProductLinesView.vue` |
| 侧栏 | 「产品线」 |
| 页面 | 只读表 + 「刷新」 |
| 列 | 编码、名称、描述、启用(`enabled` / `active` 为 false 显示「否」) |
| 分页 | 同集成环境 |
---
## 3. 无侧栏:认证与异常页
| 路由 | 页面 | 内容 |
|------|------|------|
| `/login` | `LoginView.vue` | 标题「客户商务与交付管理平台(I1)」;用户名、密码;登录 loading;演示账号提示 |
| `/403` | `ForbiddenView.vue` | Result:无权限;「返回首页」 |
| `/*` | `NotFoundView.vue` | Result404;「返回首页」 |
---
## 4. Figma / 画板拆分建议
1. **App Shell**:侧栏 + 顶栏 + 内容槽(与各列表/详情组合)。
2. **一级画板**:首页、各侧栏列表页、登录、403、404。
3. **二级画板**(无侧栏直达):合同向导(3 步)、合同详情、交付新建、交付详情、许可新建、许可详情、Callback 详情。
4. **模态层**:客户表单、项目表单、合同行、交付行;各类二次确认可在设计注释中说明。
---
## 5. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-07 | 初版:按 `web/delivery-platform-ui` 源码走查整理,供设计跟进。 |
@@ -0,0 +1,97 @@
# 平台前后端 × 客户端 SDK — 并行迭代执行索引
> **目的**:在 [BPM 与版本排期](../chuangfei-platform-bpm-and-roadmap.md) 的 **I1I6 / V1.1 / Mid / V2.0** 框架下,**三条工程线并行**落地时的 **入口文档、同步点与职责**。
> **基准**:与 [WORKSPACE_ENGINEERING_LAYOUT.md](./WORKSPACE_ENGINEERING_LAYOUT.md)Fat JAR、禁止服务端 `craftlabs-auth-bitanswer`)一致。
> **全景**:系统上下文、容器、组件与时序见 [SYSTEM_ARCHITECTURE.md](./SYSTEM_ARCHITECTURE.md)。
> **安全**:信任边界、威胁缓解与分组件控制见 [SYSTEM_ARCHITECTURE.md §9](./SYSTEM_ARCHITECTURE.md)。
---
## 1. 三条轨道与文档
| 轨道 | 仓库/工作区 | 执行包文档 |
| -------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| **A. 平台后端** | `craftlabs-delivery-platform` + `craftlabs-license-webhook`(规划) | [tracks/01-backend-platform-webhook.md](./tracks/01-backend-platform-webhook.md) |
| **B. 平台前端** | `craftlabs-delivery-platform-ui`(规划) | [tracks/02-frontend-platform-ui.md](./tracks/02-frontend-platform-ui.md) |
| **C. 客户端 SDK** | 本工作区 `craftlabs-authorization-sdk` | [tracks/03-client-sdk.md](./tracks/03-client-sdk.md) |
---
## 2. 并行模型(同迭代日历)
三轨 **共用** Roadmap 的 **I1I6**(各约 **2 周**),同一迭代内并行开工;**硬耦合**集中在 **I5Callback + Schema****I6UAT**
| 迭代 | 后端(双 JAR) | 前端(Vue) | 客户端 SDK(本仓) |
| --- | ---------------------------------------------------- | ------------------------- | -------------------------------------------- |
| I1 | 身份 + Webhook 脚手架 | 登录 + 布局壳 + RBAC 路由 | 文档/边界说明,无阻塞发布 |
| I2 | M1 主数据 + 字典 | 客户/项目页 | 同上 |
| I3 | M2 合同 + M10-F01 | 合同向导与行项 | 同上 |
| I4 | M3 交付 + M4 SN | 交付 + SN 页 | 文档与示例与 BP-10 口径一致 |
| I5 | M5 Inbox + M6 + Webhook 生产链 | Callback + 集成只读页 | **Schema + AuthConfigs + examples 对齐 BP-10** |
| I6 | 加固 + UAT | E2E + 缺陷 | **冻结 SDK 版本 + CHANGELOG + 兼容矩阵** |
| I7 | Webhook **异步投递** + **OPS** 运营权限(Callback) | 路由/菜单与 `@PreAuthorize` 对齐 | 若涉及 Schema 仍走轨道 C |
| I8 | `**DEAD` 出库重放**Webhook 内部 API + 平台代理 + UI / Runbook | Callback 详情「重新入队」 | 不涉及 SDK Schema |
| I9 | **出库状态只读**Webhook `GET by-receipt` + 平台代理 + 详情展示 | Callback 详情「出库状态」 | 不涉及 SDK Schema |
设计/复盘:[I7_DESIGN.md](./iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](./iterations/I7_IMPLEMENTATION_REVIEW.md)I8[I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)、[I8_IMPLEMENTATION_REVIEW.md](./iterations/I8_IMPLEMENTATION_REVIEW.md)I9[I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)、[I9_IMPLEMENTATION_REVIEW.md](./iterations/I9_IMPLEMENTATION_REVIEW.md)。
---
## 3. 跨轨同步点(必须对齐)
| 周次/迭代 | 同步内容 | 参与方 |
| -------- | ------------------------------------------------------------------------ | ------------------------------------------- |
| **I1 末** | 认证方式(JWT vs Session)、OpenAPI `auth` tag、错误码与 401 行为 | 后端 + 前端 |
| **I2 末** | 客户/项目 DTO 冻结;字典编码 | 后端 + 前端 |
| **I3 末** | 合同状态枚举与非法迁移码;M10-F01 字段 | 后端 + 前端 |
| **I4 末** | SN 绑定与「孤儿 SN」规则;交付门禁参数(M11-F20) | 后端 + 前端 + SDK(文档) |
| **I5 起** | Callback payload 与 inbox DTO**Idempotency-Key**Webhook→平台投递方式(HTTP/MQ | 后端 Webhook + 后端平台 + 前端 + **SDKSchema/示例)** |
| **I6** | UAT 场景、冻结 **SDK 版本**、两枚 Fat JAR 与前端 `VITE_API_BASE` | 全员 |
**契约 Owner(建议角色)**:架构或 Tech Lead 兼任;**OpenAPI 单一事实来源** 为本仓库 `[contracts/openapi/delivery-platform-api.json](../../contracts/openapi/delivery-platform-api.json)`(与运行时 `/v3/api-docs``OpenApiContractSnapshotTest` 对齐),说明见 `[contracts/README.md](../../contracts/README.md)`
---
## 4. 依赖关系简图
```mermaid
flowchart LR
subgraph FE[前端 UI]
UI[Vue 3]
end
subgraph BE[平台 API]
API[platform-bootstrap.jar]
end
subgraph WH[Webhook]
WB[webhook-bootstrap.jar]
end
subgraph SDK[本仓库 SDK]
JAR[craftlabs-auth-*]
NAT[native]
end
UI --> API
WB -->|内部调用或 MQ| API
SDK -.->|不依赖平台运行时| API
JAR --> NAT
```
---
## 5. 修订记录
| 日期 | 说明 |
| ---------- | ----------------------------------------- |
| 2026-04-06 | 初版:三轨并行索引 + 同步点 + Gantt 示意。 |
| 2026-04-07 | 增加 **I9**Webhook 出库状态只读(Callback 详情可观测)。 |
+562
View File
@@ -0,0 +1,562 @@
# 系统架构设计 — 全景图与组件清单
> **角色**:资深架构师视角的 **系统上下文、容器划分、逻辑组件、数据流与部署边界** 的单一汇总页。
> **读者**:研发、测试、运维、产品与集成方。
> **关联**[工作区工程划分](./WORKSPACE_ENGINEERING_LAYOUT.md) · [并行迭代索引](./PARALLEL_ITERATION_INDEX.md) · [功能模块 M1M11](../chuangfei-platform-product-modules.md) · [比特对接总览](../chuangfei-bitanswer-integration-platform.md) · [`engineering/workspace-manifest.json`](../../engineering/workspace-manifest.json)
> **安全专章**:本章 **§9**(含 **§9.0** 小团队阶段、**§9.8** SDK 发布完整性);与工作区 [WORKSPACE §10 安全边界](./WORKSPACE_ENGINEERING_LAYOUT.md) 互补。
---
## 1. 架构目标与原则
| 原则 | 说明 |
|------|------|
| **边界清晰** | **客户端授权运行时**SDK + Native + 现场进程)与 **商业交付平台**(Web + 双后端 + 数据持久化)在 **依赖、发布周期、classpath** 上严格分离。 |
| **契约先行** | 客户端配置以 **`schemas/craftlabs-auth-config.schema.json`** 为契约;Callback / OpenAPI 以 **文档与版本化契约** 对齐多轨迭代(见 [PARALLEL_ITERATION_INDEX](./PARALLEL_ITERATION_INDEX.md))。 |
| **可运维** | 每个可独立扩缩的后端服务 **一枚 Fat JAR**`spring-boot-maven-plugin` **repackage** 仅挂在启动模块);密钥与连接串 **不进** SDK 与示例仓库。 |
| **数据栈锁定** | 平台持久化:**PostgreSQL 15** + **MyBatis-Plus**(详见 [WORKSPACE_ENGINEERING_LAYOUT §9.1](./WORKSPACE_ENGINEERING_LAYOUT.md))。 |
| **安全默认** | **最小权限**、**纵深防御**、**可审计**;对外入口 **TLS**;敏感面(Webhook、管理 API、SN/许可数据)**单独威胁建模**与门禁。 |
| **阶段目标(创飞当前)** | **小团队**:优先 **功能与需求闭环**;并发与规模 **不高**。平台侧 **单/双 Fat JAR + 单机或少量 VM**`java -jar` 即可;**不将 K8s、云 KMS、服务网格** 作为现阶段必选项,待业务量与合规要求上升再演进。 **安全重心** 放在 **已发布 SDK 的完整性**(不易被 **替换、假冒**),见 **§9.0 / §9.8**。 |
---
## 2. 系统上下文(C4 Level 1
展示本体系与 **外部环境** 的关系:谁使用谁、谁回调谁。
```mermaid
flowchart TB
subgraph Actors["参与者"]
OPS[运维/管理员]
BIZ[商务/交付/License Ops]
DEV[客户侧研发]
end
subgraph CraftLabs["创飞侧系统(本方案范围)"]
UI[交付管理平台 Web UI]
API[交付平台 API]
WH[License Webhook Ingress]
DB[(PostgreSQL 15)]
end
subgraph ClientSite["客户现场 / 边缘环境"]
APP[客户业务应用]
SDK[CraftLabs 授权 SDK]
NAT[BitAnswer Native 库]
end
subgraph External["外部系统"]
BA[比特安索云 / 控制台]
end
OPS --> UI
BIZ --> UI
UI --> API
API --> DB
WH --> DB
WH -->|内部 HTTP / MQ 异步| API
BA -->|规则 Callback HTTPS POST| WH
SDK -->|Bit_Login / 许可 API| BA
APP --> SDK
SDK --> NAT
DEV -->|依赖 Maven 工件 + JSON 配置| SDK
DEV -.->|阅读 Schema/文档| CraftLabs
```
**读图要点**
- **比特** 只与 **Webhook**(服务端入站)和 **SDK**(客户端出站授权)发生 **不同方向** 的交互;平台 UI **不** 替代 SDK 完成现场授权。
- **SDK 不依赖** 平台 API 运行时;二者仅通过 **契约(Schema、文档、可选 OpenAPI 导出配置)** 弱耦合。
---
## 3. 容器架构(C4 Level 2
本工作区与规划仓库中的 **可构建、可部署或可分发单元**
```mermaid
flowchart TB
subgraph Browser["用户浏览器"]
SPA[Vue 3 SPA<br/>delivery-platform-ui]
end
subgraph DMZ["接入层(可选 Gateway"]
GW[API Gateway / 反向代理<br/>规划]
end
subgraph SimpleHost["机房或云主机(单机为主)"]
JAR_API[delivery-platform-api<br/>Fat JAR :8080]
JAR_WH[license-webhook-ingress<br/>Fat JAR :8081]
PG[(PostgreSQL 15)]
MQ[[消息队列<br/>可选·低并发可省]]
end
subgraph ArtifactRepo["制品与契约"]
MVN[Mavencn.craftlabs / cn.craftlabs.platform]
SCH[JSON Schema + examples]
NATIVE[各 OS Native 动态库]
end
subgraph CI["持续集成"]
WF_SDK[ci-java.yml]
WF_PLAT[ci-platform.yml]
end
SPA -->|HTTPS /api| JAR_API
GW -.-> JAR_API
GW -.-> JAR_WH
JAR_API --> PG
JAR_WH --> PG
JAR_WH -.-> MQ
MQ -.-> JAR_API
MVN --> APPX[客户应用构建]
SCH --> APPX
NATIVE --> APPX
WF_SDK --> MVN
WF_PLAT --> JAR_API
WF_PLAT --> JAR_WH
WF_PLAT --> SPA
```
**阶段说明(创飞当前)**:并发不高时 **可不部署 MQ**Webhook 直写库或由 API 轮询即可);后端以 **一台或两台云主机** 各跑 `java -jar …` 为主,**无需 K8s**。
| 容器 | 技术 / 形态 | 默认端口或产出 | 职责摘要 |
|------|-------------|----------------|----------|
| **delivery-platform-ui** | Vue 3 + Vite + Pinia + Element Plus | 静态资源 / dev server | 管理端 SPA:登录、领域页面、调用平台 REST。 |
| **delivery-platform-api** | Spring Boot 3.4.x(目标 4.0.x)、Security、Actuator、MyBatis-Plus | **8080**、Fat JAR | 领域 API、认证、M1–M11 业务能力、读写字段库。 |
| **license-webhook-ingress** | Spring Boot、Actuator | **8081**、Fat JAR | 比特 Callback 入站、验签/Token、快速 2xx、异步投递(表/MQ)。 |
| **PostgreSQL** | 15.x | 5432 | 平台主库;与 SDK **无** 直连。 |
| **SDK Java 模块** | 多模块 Maven**非** Spring Boot | 多枚 `craftlabs-auth-*.jar` | 配置模型、BitAnswer 桥接、自托管占位等。 |
| **Native** | CMake | `.so` / `.dylib` / `.dll` | 比特官方运行时绑定,随 SDK 版本发布。 |
| **schemas + examples** | JSON Schema、样例 JSON | 无进程 | 配置契约与集成样例。 |
---
## 4. 平台后端逻辑组件(领域视图)
与产品模块 **M1M11** 对齐的 **逻辑分包**(实现可跨多 Maven 模块,但边界应一致)。
```mermaid
flowchart LR
subgraph API["delivery-platform-api(逻辑)"]
M11[identity / platform M11]
M1[customer-project M1]
M2[contract M2]
M3[delivery M3]
M4[licensing SN M4]
M5[inbox callback M5]
M6[integration mapping M6]
M7[device M7]
M8[notification M8]
M9[reporting M9]
M10[audit M10]
PERS[persistence MyBatis-Plus]
end
M11 --> M1
M1 --> M2
M2 --> M3
M2 --> M4
M3 --> M4
M6 --> M4
M5 --> M4
M4 --> M7
M5 --> M8
M4 --> M9
M1 & M2 & M3 & M4 & M5 --> M10
M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & M9 & M10 & M11 --> PERS
```
**Webhook 侧(license-webhook-ingress)逻辑组件**(精简):
| 组件 | 职责 |
|------|------|
| **Ingress Controller** | 接收 POST、限流与尺寸约束(规划加固)。 |
| **Token / 签名验证** | `x-bitanswer-token` 或约定头;与 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` 等环境变量对齐。 |
| **幂等与投递** | `Idempotency-Key` 或 payload 内事件 ID;写入 **inbox****MQ**(I5+),由平台 API 消费。 |
| **观测** | Actuator、结构化日志、指标(与平台统一约定)。 |
---
## 5. 客户端 SDK 组件(本仓库 `java/` + `native/`
```mermaid
flowchart TB
subgraph SDK["CraftLabs Authorization SDK"]
CORE[craftlabs-auth-core<br/>配置 / NativeBridge / 校验]
BIT[craftlabs-auth-bitanswer<br/>Provider + loadLibrary]
SH[craftlabs-auth-selfhosted<br/>占位实现]
TST[craftlabs-auth-tests]
end
SCH2[schemas + examples]
JNI[libcraftlabs_auth_bitanswer]
CORE --> BIT
CORE --> SH
BIT --> JNI
TST --> CORE
SCH2 -.-> CORE
```
**硬约束**:平台服务端 **禁止** 依赖 `craftlabs-auth-bitanswer`**禁止** `System.loadLibrary("craftlabs_auth_bitanswer")`(见 [WORKSPACE §5](./WORKSPACE_ENGINEERING_LAYOUT.md))。
---
## 6. 关键运行时序
### 6.1 比特 Callback → 平台
```mermaid
sequenceDiagram
participant BA as 比特安索云
participant WH as license-webhook-ingress
participant Store as PG / MQ
participant API as delivery-platform-api
participant UI as 管理端 UI
BA->>WH: POST /webhook/bitanswer/callback
WH->>WH: 校验 Token / 幂等键
WH->>Store: 持久化或入队(异步)
WH-->>BA: 2xx 快速响应
API->>Store: 拉取 / 消费事件
UI->>API: 查询 Inbox / 处置状态
```
### 6.2 管理员使用平台
```mermaid
sequenceDiagram
participant U as 用户
participant UI as delivery-platform-ui
participant API as delivery-platform-api
participant DB as PostgreSQL 15
U->>UI: 登录 / 操作业务页
UI->>API: RESTSession / JWT 由迭代约定)
API->>DB: MyBatis-Plus 读写
API-->>UI: DTO / 错误码
```
### 6.3 客户现场授权(与平台解耦)
```mermaid
sequenceDiagram
participant APP as 客户应用
participant SDK as craftlabs-auth-*
participant NAT as Native
participant BA as 比特安索云
APP->>SDK: 初始化 + 加载配置 JSON
SDK->>SDK: Schema/规则校验(可选)
SDK->>NAT: JNI 调用
NAT->>BA: 许可 / 登录协议
BA-->>NAT: 授权结果
NAT-->>SDK: 回调结果
SDK-->>APP: 授权状态 / 特征项
```
---
## 7. 全量组件清单(Master Inventory
下表覆盖 **本工作区已实现或已占位** 的组件;规划项标注为 **(规划)**。
| ID | 组件名称 | 类型 | 路径 / 坐标 / 产物 | 技术栈 | 主要职责 |
|----|----------|------|-------------------|--------|----------|
| C01 | **craftlabs-auth-parent** | Maven 聚合 | `java/pom.xml` | Java 17 | SDK 父工程、依赖与模块编排。 |
| C02 | **craftlabs-auth-core** | Library JAR | `java/craftlabs-auth-core` | Java | 配置模型、`NativeBridge`、与 Schema 对齐的校验入口。 |
| C03 | **craftlabs-auth-bitanswer** | Library JAR | `java/craftlabs-auth-bitanswer` | Java + JNI | `BitAnswerProvider``System.loadLibrary`、**仅客户端**。 |
| C04 | **craftlabs-auth-selfhosted** | Library JAR | `java/craftlabs-auth-selfhosted` | Java | 自托管授权占位 / 扩展点。 |
| C05 | **craftlabs-auth-tests** | 测试模块 | `java/craftlabs-auth-tests` | JUnit 等 | SDK 侧集成与回归测试。 |
| C06 | **libcraftlabs_auth_bitanswer** | Native | `native/` | CMake | 各平台动态库,与 C02/C03 **同版本发布**。 |
| C07 | **craftlabs-auth-config Schema** | 契约 | `schemas/craftlabs-auth-config.schema.json` | JSON Schema Draft 2020-12 | 客户端配置 **单一契约**;CI 与 SDK 测试可对齐校验。 |
| C08 | **examples** | 样例 | `examples/` | JSON / 脚本 | 配置样例与烟测引用。 |
| C09 | **documentation** | 文档 | `docs/` | Markdown | 产品、对接、工程、架构。 |
| C10 | **engineering-meta** | 元数据 | `engineering/` | JSON / MD | `workspace-manifest.json`、planned 占位、README 索引。 |
| C11 | **craftlabs-platform-services-parent** | Maven 聚合 | `services/pom.xml` | Java 17 + Spring Boot 3.4.x | 平台后端父 POM**与 C01 分构建线**。 |
| C12 | **delivery-platform-api** | 可执行 Fat JAR | `services/delivery-platform-api` | Spring Web/Security/Actuator、MyBatis-Plus、PostgreSQL 驱动 | 平台 REST、领域逻辑(迭代扩展)、**8080**。 |
| C13 | **license-webhook-ingress** | 可执行 Fat JAR | `services/license-webhook-ingress` | Spring Web/Actuator | Callback 入站、Token、**8081**。 |
| C14 | **PostgreSQL** | 基础设施 | 部署环境 / `services/docker-compose.yml` | 15.x | 平台主数据存储。 |
| C15 | **delivery-platform-ui** | SPA | `web/delivery-platform-ui` | Vue 3、Vite、Pinia、vue-router、axios、Element Plus | 管理端界面;dev 代理 `/api` → API。 |
| C16 | **CISDK Java** | 流水线 | `.github/workflows/ci-java.yml` | GitHub Actions | `java/` 构建与测试(JDK 版本以 workflow 为准)。 |
| C17 | **CIPlatform + Web** | 流水线 | `.github/workflows/ci-platform.yml` | GitHub Actions | `services/` Maven verify + `web/` npm build。 |
| C18 | **API Gateway** | 接入(规划) | 未在本仓 | 待定 | 统一 TLS、路由、限流(可选)。 |
| C19 | **Message Queue** | 中间件(规划) | 未在本仓 | 待定 | Webhook 与 API 解耦、削峰(I5+ 常见形态)。 |
| C20 | **企业 IdP** | 身份(规划) | 未在本仓 | OIDC/SAML 等 | 与 M11 企业登录集成(若需)。 |
| C21 | **密钥与凭据** | 运维配置 | 部署机 | **环境变量**、**chmod 600 配置文件** | 注入 Webhook Token、DB 密码;**禁止** 进 Git**不强制** 云 KMS。 |
| C22 | **WAF / 边缘限流** | 安全(可选) | 边界 | 云 WAF 或 Nginx 限流模块 | 低并发阶段可省;对外暴露或合同要求时再上。 |
---
## 8. 技术栈总览
| 分层 | 选型 | 备注 |
|------|------|------|
| 管理端 | Vue 3、Vite、Pinia、vue-router、axios、Element Plus | 构建产物静态托管或 CDN。 |
| 平台 API | Spring Boot 3.4.x(路线图 4.0.x)、Spring Security、Actuator | Fat JAR 部署。 |
| Webhook | 同上(独立进程) | 与 API **进程隔离**,失败域分离。 |
| ORM | MyBatis-Plus`mybatis-plus-spring-boot3-starter` | Boot 4 时切换 `boot4-starter`。 |
| 数据库 | PostgreSQL 15 | 测试可用 H2 PG 模式或 Testcontainers。 |
| 客户端 SDK | Java 17、多模块薄 JAR | 不打 Spring Boot repackage。 |
| 客户端 Native | CMake、比特官方 API | 与 Java **同版本 tag**。 |
| 契约 | JSON Schema 2020-12 | `schemas/``examples/`。 |
---
## 9. 安全架构(授权与平台)
授权与许可数据直接影响 **商业履约与合规**,安全需求高于一般内部后台。本节给出 **信任边界、威胁面、控制措施与演进要求**,供架构评审与渗透测试对齐。
### 9.0 阶段与重心(广州创飞 · 小团队)
| 维度 | 当前立场 |
|------|----------|
| **部署** | **单 Fat JAR / 进程** 部署在云主机或内网服务器;PostgreSQL 可与应用 **同机或同 VPC 内另一台**;**不强制** 容器编排与云 KMS。 |
| **密钥** | Webhook Token、数据库密码等放在 **宿主机环境变量****权限受限的配置文件**`chmod 600`、仅运行用户可读),**绝不** 提交 Git。 |
| **优先级** | 先保证 **功能与需求闭环**;平台侧 **基础 TLS + 认证 + 日志脱敏** 即可满足多数早期场景。 |
| **安全重心** | **对外发布的 SDKJAR + Native**:防范 **供应链篡改与假冒制品****破解/逆向** 的硬防线主要在 **比特安索 Native 与许可密码学**Java 层需 **诚实评估**(见 **§9.8**)。 |
### 9.1 安全目标(CIA + 授权特有)
| 目标 | 含义 | 在本体系的落点 |
|------|------|----------------|
| **机密性** | 许可状态、SN、合同金额、Callback 原文、密钥不外泄 | TLS、日志脱敏、DB 访问控制、前端不持久化敏感令牌到不安全存储 |
| **完整性** | Callback 与台账不被篡改;配置与制品未被替换 | Webhook 验签/共享密钥、幂等键、审计日志(M10)、依赖与镜像签名(供应链) |
| **可用性** | 授权链路与管理端在约定 SLA 内可服务 | Webhook 快速 2xx + 异步处理;低并发阶段 **单机 + 健康检查** 即可,**不必** 预设 K8s 弹性扩容 |
| **不可否认性**(按需) | 关键操作可追溯 | 审计表、WORM 或日志外送(合规场景) |
### 9.2 信任边界
```mermaid
flowchart TB
subgraph Untrusted["不可信或半可信"]
Internet((Internet))
BA[比特云]
CustomerNet[客户现场网络]
Dev[集成方研发环境]
end
subgraph TrustHigh["高信任 — 创飞数据面"]
PG[(PostgreSQL)]
Secrets[(凭据:宿主机环境变量<br/>或受限配置文件)]
end
subgraph TrustMed["中信任 — 应用进程"]
API[delivery-platform-api]
WH[license-webhook-ingress]
end
subgraph TrustUser["用户终端"]
Browser[管理员浏览器]
ClientApp[客户业务进程 + SDK]
end
Internet -->|TLS 仅| WH
Internet -->|TLS 仅| API
Internet --> Browser
BA -->|HTTPS Callback| WH
CustomerNet --> ClientApp
Dev -.->|Maven/文档| ClientApp
Browser -->|HTTPS + 会话/JWT| API
ClientApp -->|TLS 至比特/自托管| BA
WH --> PG
API --> PG
API --> Secrets
WH --> Secrets
```
**边界规则**
- **Webhook URL** 暴露在公网或 DMZ:视为 **持续被扫描**;必须 **TLS + 强认证 + 限流 + 最小响应体**
- **管理端 API**:仅对 **已认证主体** 开放业务写能力;**禁止** 将平台账号用于客户现场 SDK。
- **SDK 配置 JSON**:含 `bitanswer.url``applicationData`、特征映射等,属 **客户环境机密**;**不得** 回传至创飞平台作为默认遥测(除非单独签署数据处理协议并做脱敏设计)。
### 9.3 威胁面与缓解(摘要)
| 威胁 | 示例 | 架构层缓解 |
|------|------|------------|
| **伪造 Callback** | 攻击者 POST 恶意 payload | **共享密钥**(如 `x-bitanswer-token``CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` 常量时间比较);**比特官方 IP/证书钉扎**(若文档支持);**payload 大小与 JSON 深度限制** |
| **重放** | 重复投递同一事件污染台账 | **幂等键**`Idempotency-Key` / 事件 ID)+ 唯一约束;重复返回成功语义 |
| **撞库 / 会话窃取** | 管理端弱口令、Token 泄露 | **密码策略 / MFA(规划)**、**HttpOnly + Secure Cookie** 或 **短期 JWT + 刷新**、**CORS 严格白名单** |
| **越权** | 用户访问他人合同或 SN | **RBACM11**、**行级授权**(组织/客户维度)、API **资源级鉴权** |
| **注入** | SQL / 反序列化 | **参数化查询**MyBatis `#{}`)、**禁用不安全的反序列化 gadget**、DTO 白名单 |
| **SSRF**(若存在出站拉取) | 服务端替用户请求内网 | **出站 URL 白名单**、**禁用 file://**、独立网络分区 |
| **供应链 / 假冒 SDK** | 第三方镜像站替换 JAR、客户误下「绿色版」 | **官方唯一分发渠道** + **GPG/SHA-256 校验**(§9.8);Maven 依赖 **锁定版本**CI **依赖漏洞扫描**(力所能及) |
| **日志泄露** | Callback 原文、Token 打日志 | **结构化日志字段脱敏**、**禁止 debug 打印完整 Authorization** |
### 9.4 分组件控制要求
#### 9.4.1 `license-webhook-ingress`
| 控制项 | 要求 |
|--------|------|
| 传输 | **仅 HTTPS**;HTTP 仅允许本机健康检查(若必须)且不得承载业务 |
| 认证 | 配置 `CRAFTLABS_WEBHOOK_EXPECTED_TOKEN`**强制校验** `x-bitanswer-token`(或比特约定头);生产 **禁止** 长期关闭校验 |
| 处理 | **先校验后解析大对象**;限制 body 大小;超时快速失败仍返回约定错误码(避免资源耗尽) |
| 观测 | 日志记录 **事件 ID、幂等键、结果码**;**不** 记录完整密钥与完整 payload(或脱敏后) |
| 网络 | 云厂商 **安全组** 仅开放 443/业务端口;数据库 **不对公网**;单机阶段 **本机回环或内网 IP** 连库即可 |
#### 9.4.2 `delivery-platform-api`
| 控制项 | 要求 |
|--------|------|
| 认证 | I1 演示账号 **不得用于生产**;生产至少 **强口令**;团队具备条件时再上 **MFA / 企业 IdPC20** |
| 授权 | 每个 mutating 接口 **显式角色/权限**;与 M11 角色矩阵一致 |
| 数据 | DB 账号 **最小权限**(读写分离账号可选);连接串来自 **环境变量或宿主机配置**,禁止提交仓库 |
| Actuator | **生产关闭或仅本机访问** `env``heapdump``health` 可给 **本机监控脚本或 Nginx 探活** |
| CORS | **显式允许源**,禁用 `*` 携带凭证 |
#### 9.4.3 `delivery-platform-ui`
| 控制项 | 要求 |
|--------|------|
| 存储 | **不** 将 refresh token 写入 `localStorage`(优先 HttpOnly Cookie 或安全内存策略) |
| CSP | 生产启用 **Content-Security-Policy**,限制内联脚本与未知域 |
| 依赖 | `npm audit` 纳入 CI;锁定 lockfile |
#### 9.4.4 客户端 SDK 与 Native
| 控制项 | 要求 |
|--------|------|
| 配置 | `craftlabs-auth-config`**宿主应用** 安全加载(文件权限、必要时磁盘加密);**禁止** 将含敏感字段的示例提交到客户代码库模板 |
| 完整性 | Native **与 JAR 同版本** 分发;**正式发布** 必须带 **校验与/或签名**(见 **§9.8** |
| 运行时 | **不信任** 客户进程内存防护;敏感字符串尽量 **缩短生命周期**(比特 SDK 能力范围内) |
#### 9.4.5 PostgreSQL
| 控制项 | 要求 |
|--------|------|
| 网络 | DB **不暴露公网**;仅应用子网可达 |
| 加密 | 云盘默认加密或宿主机全盘加密即可;敏感列 **应用层加密** 为可选增强 |
| 审计 | 高危 DML/DDL 与 **管理员账号** 变更记入 M10 或 DB 审计扩展 |
### 9.5 与时序对齐的安全步骤(示意)
**Callback(补充安全步骤)**
```mermaid
sequenceDiagram
participant BA as 比特安索云
participant WH as Webhook
participant Store as PG/MQ
BA->>WH: TLS + POST
WH->>WH: TLS 终结校验(mTLS 仅在高合规客户场景考虑)
WH->>WH: 校验 Token(常量时间)
WH->>WH: 限制 body 大小 + 解析
WH->>WH: 幂等键去重
WH->>Store: 写入 inbox(脱敏日志)
WH-->>BA: 2xx
```
**管理端(补充安全步骤)**
```mermaid
sequenceDiagram
participant U as 用户
participant UI as SPA
participant API as Platform API
U->>UI: 登录
UI->>API: HTTPS + CSRF 防护(Cookie 模式)
API->>API: 认证 + 颁发会话/JWT
UI->>API: 业务请求 + 凭证
API->>API: 鉴权(角色 + 资源)
API-->>UI: 最小必要 DTO
```
### 9.6 合规与审计衔接
- **M10 审计与合规**:登录、合同变更、SN 绑定/解绑、Callback 处置、权限变更等 **必须** 产生不可抵赖审计记录(谁、何时、对象 ID、前后摘要)。
- **数据出境**:若 Callback 或日志含个人信息,需对齐 **个人信息保护法** 与客户 DPA。
- **渗透测试**:有对外暴露或合同要求时,对 **Webhook****管理 API** 做专项测试(认证绕过、IDOR、批量枚举);小团队可 **采购外包渗透** 或分阶段补做。
### 9.7 架构评审安全清单(节选)
- [ ] Webhook 生产环境 **已启用** 共享密钥或更强机制(签名/HMAC 以比特文档为准)。
- [ ] 管理 API **无** 默认弱口令路径;Actuator 敏感端点 **已收敛**
- [ ] 平台与 Webhook **未引入** `craftlabs-auth-bitanswer`
- [ ] 日志与异常栈 **无** 明文数据库密码、Token、完整 Callback。
- [ ] OpenAPI 中 **错误码** 不泄露内部堆栈或枚举用户存在性(登录接口需谨慎)。
- [ ] `examples/config/*.json` **无** 真实生产 URL/密钥(仅占位)。
- [ ] **SDK 发布**:每个对外版本具备 **SHA-256 清单****强烈建议** GPG 签名与 **唯一官方下载说明**
- [ ] **SDK 发布**`java``native` **同 tag**CHANGELOG 与交付说明 **可追溯**
### 9.8 SDK 发布完整性(当前安全重心)
客户侧 **Java 字节码可被反编译**,单靠混淆无法对抗动机足够的攻击者。**真正承担许可与密码学校验的**,应是 **比特安索 Native 与云端许可体系**。创飞 SDK 的安全目标应定位为:
1. **防篡改、防假冒**:客户拿到的是 **官方构建** 的 JAR/Native,未被中间人替换。
2. **防版本错配**JAR 与 `.so/.dylib/.dll` **同一次发布**(同 Git tag / 同一 Release 页),避免「新 JAR + 旧库」导致异常或绕过路径。
3. **可追溯**:出问题能对照 **确切版本与校验和** 复现。
| 措施 | 说明 | 小团队可执行性 |
|------|------|------------------|
| **官方唯一渠道** | 仅通过 **公司控制的 Maven 私服 / GitHub Release / 合同交付包** 分发;**禁止** 引导客户从匿名网盘、论坛下载「整合包」。 | 必做 |
| **SHA-256 清单** | 每个 Release 发布 `SHA256SUMS`;仓库内 **`scripts/sdk-release-checksums.sh`** 已落地,说明见 **`java/RELEASING.md`**;客户用 `sha256sum -c` / `shasum -a 256 -c` 校验。 | 必做 |
| **GPG 签名** | Maven**`mvn -f java/pom.xml -Prelease-sign verify`**`maven-gpg-plugin`,各 JAR 旁生成 `.asc`);清单文件:`SIGN=1` 跑上述脚本生成 `SHA256SUMS.asc`。公布 **公钥指纹** 固定页。 | 强烈建议 |
| **Git Tag = 发布单元** | `java/``native/` **同一 tag** 打发布;CHANGELOG 写清 **兼容的比特 SDK 版本**。 | 必做 |
| **依赖锁定** | 客户工程使用 **明确版本号** 或 BOM;避免「浮动版本」被投毒依赖间接替换。 | 建议 |
| **ProGuard 等混淆** | 仅增加逆向成本,**不能**替代 Native 侧安全;若上,只作 **非关键路径** 的辅助手段。 | 可选 |
| **法务条款** | 许可协议中约定 **禁止反编译、再分发**(民事约束,与技术防护互补)。 | 建议 |
**客户集成检查清单(可印在交付文档)**
- [ ] JAR/Native 均来自 **创飞官方渠道**,并已核对 **SHA-256**(或验签)。
- [ ] 各工件版本号 **一致**,与发布说明中的 **比特依赖版本** 一致。
- [ ] 未混入第三方「破解补丁」或修改版 `libcraftlabs_auth_bitanswer`
---
## 10. 部署与网络(简图)
```mermaid
flowchart TB
subgraph Public["公网 / 比特侧可达"]
BA[比特 Callback 出口 IP]
end
subgraph Perimeter["边界(常为一台 Nginx/Caddy"]
LB[TLS 终结 + 反代]
end
subgraph AppTier["应用层(单机或双机 java -jar"]
WH_P[Webhook Fat JAR]
API_P[Platform API Fat JAR]
UI_S[静态 UI 或同机 Nginx 托管]
end
subgraph DataTier["数据层"]
PG_P[PostgreSQL 15<br/>单实例或云托管]
end
BA --> LB --> WH_P
Users((管理员浏览器)) --> LB --> UI_S
Users --> LB --> API_P
WH_P --> PG_P
API_P --> PG_P
```
**阶段注记(创飞当前)****不依赖 K8s**;扩容以 **加机器 + 多实例反代** 即可。**安全注记**:云安全组 **收紧**PG **仅内网**WAF/mTLS **按合同与预算** 选装,非 MVP 必选。
---
## 11. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:系统上下文、容器、领域与 SDK 组件图、时序、全量组件表、技术栈与部署简图。 |
| 2026-04-06 | **§9 安全架构**:信任边界、威胁与缓解、分组件控制、安全时序、合规与评审清单;原则表增加 **安全默认**;部署节增加安全注记。 |
| 2026-04-06 | **对齐小团队**:§1 **阶段目标**、§3 单机部署说明、§9.0、凭据改为宿主机配置、部署图改为 `java -jar` + Nginx;新增 **§9.8 SDK 发布完整性**(防篡改/假冒重心)。 |
---
**下一步**:领域表结构、OpenAPI 与事件 Schema 建议单独维护为 **版本化契约仓库**`contracts/` 子模块,并在 [PARALLEL_ITERATION_INDEX](./PARALLEL_ITERATION_INDEX.md) 的同步点更新版本号。
@@ -0,0 +1,276 @@
# 工作区工程划分与仓库边界 — 架构说明
> **角色**:资深架构师对工作区现有资产与 **创飞客户商务与交付管理平台** 相关文档的对照走查结论。
> **范围**:本 Git 工作区根目录 `craftlabs-authorization-sdk` 的 **物理目录、构建边界、发布物、与外部工程的契约**;**不**在本提交中迁移 `java/`、`native/` 路径(避免破坏既有 CI 与本地脚本)。
> **关联**:[**系统架构全景(图 + 全量组件)**](./SYSTEM_ARCHITECTURE.md) · [功能模块](chuangfei-platform-product-modules.md) · [BPM 与排期](chuangfei-platform-bpm-and-roadmap.md) · [平台与比特对接](chuangfei-bitanswer-integration-platform.md) · [三轨并行执行索引](./PARALLEL_ITERATION_INDEX.md) · [根目录 `engineering/` 清单](../../engineering/workspace-manifest.json)
---
## 1. 走查结论(摘要)
| 维度 | 现状 | 架构建议 |
| --------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| **工作区身份** | 以 **客户端授权 SDK**Java + Native)为核心,配套 **Schema、示例配置、产品/平台文档** | 保持本仓库 **「授权 SDK + 契约资产」** 为一等公民;**商业交付平台**Spring Boot 4.0. + Vue**不宜**与 JNI/native 强耦在同一 Maven 树内混建 |
| **构建** | `java/` Maven 多模块;`native/` CMake 动态库 | CI **双轨**Java 与 Native 可并行;平台后端/前端 **独立仓库或独立根级目录** 时再挂接新 workflow |
| **文档** | `docs/` 已覆盖比特规则、Callback、创飞平台 BPM、模块、权限 | 文档继续集中在 `docs/`**工程边界**以本文 + `engineering/`**单一事实来源(SSOT** |
| **缺口** | 根目录无 README;平台 Webhook、管理端未在代码层体现 | 用 `**engineering/planned/`** 占位说明 **推荐独立仓库名与技术栈**;实现阶段按 manifest 开仓 |
---
## 2. 工作区逻辑分层(C4-L1
```mermaid
flowchart TB
subgraph WS["本工作区 craftlabs-authorization-sdk"]
S[SDK Java API]
N[Native craftlabs_auth_bitanswer]
SCH[schemas/ craftlabs-auth-config]
EX[examples/]
DOC[docs/]
end
subgraph EXT["规划中的独立工程(见 engineering/planned"]
WH[license-webhook-ingress]
API[delivery-platform-api]
UI[delivery-platform-ui]
end
BA[比特安索云/控制台]
CLI[现场客户端产品]
N --> S
S --> CLI
SCH --> CLI
WH -->|POST Callback| BA
API -->|读写字段| WH
UI --> API
DOC -.->|约束| SCH
```
**原则**:**运行时依赖**从平台 API → Webhook → 比特 为一条链;**客户端**仅依赖 **Maven 发布的 SDK 工件** + **JSON 配置**(校验用 Schema),不依赖平台 UI。
---
## 3. 物理目录与职责(当前实现)
| 路径 | 工程类型 | 职责 | 构建命令(摘要) |
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| `**java/`** | Maven `pom` 聚合 | `craftlabs-auth-core`(配置模型/校验/桥接)、`craftlabs-auth-bitanswer``craftlabs-auth-selfhosted``craftlabs-auth-tests` | `mvn -f java/pom.xml verify` |
| `**native/**` | CMake | `libcraftlabs_auth_bitanswer`、JNI、自研 HTTP 占位 | 见 `native/build` 或项目 README |
| `**schemas/**` | JSON Schema | `craftlabs-auth-config` 契约,与 `AuthConfigs` 对齐 | 无编译;CI 可 `ajv`/脚本校验 |
| `**examples/**` | 样例 | `config/*.json``java`/`python` 烟测 | 随测试引用 |
| `**docs/**` | 文档 | 产品、比特对接、工程架构 | 无构建 |
| `**engineering/**` | **工程元数据** | manifest、**planned** 占位、边界说明 | 无构建 |
**禁止**:在未做迁移方案前,将 **Spring Boot 应用** 塞进 `java/` 下与 `craftlabs-auth-`* 并列 —— 会导致发布周期、依赖(Boot vs 库消费者)冲突。
**平台端正确做法**:在 **独立仓库****多模块开发**,由 **唯一 `bootstrap` 模块** 执行 `spring-boot-maven-plugin` **repackage**,对外只发布 **一枚 Fat JAR**(见 **§5.3**)。
---
## 4. Maven 模块边界(SDK 内部)
```
craftlabs-auth-parent
├── craftlabs-auth-core # 无第三方授权运行时;配置、NativeBridge 接口
├── craftlabs-auth-bitanswer # System.loadLibrary + BitAnswerProvider
├── craftlabs-auth-selfhosted
└── craftlabs-auth-tests # 集成/配置测试
```
- **对外发布坐标**:以 `cn.craftlabs:craftlabs-auth-*` 为准;版本与 **changelog** 在发布流水线维护。
- **平台后端**若需读取同一配置模型:优先 **复制 DTO + 共用 Schema**,或将来抽取 `**craftlabs-auth-config-model` 极小 jar**(另案);**禁止**在平台可执行 JAR 中引入 `craftlabs-auth-bitanswer`(会拉起 JNI/native 期望,属边界错误)。
---
## 5. 客户端 SDK 与平台端:深入评估与防混淆
### 5.1 二者不可混为一谈
| 维度 | **客户端 SDK**(本仓库 `java/` + `native/` | **平台端后端**`delivery-platform-api` 等独立工程) |
| --------------- | ----------------------------------------- | ---------------------------------------------------------------- |
| **运行位置** | 客户现场业务进程 / 边缘 AI 设备 | 创飞机房 / 云(VPC) |
| **核心职责** | `Bit_Login`、特征项、本地/云授权运行时 | 合同、交付、SN 台账、Callback 处置、RBAC、报表 |
| **典型依赖** | BitAnswer 官方 native + `BitAnswerProvider` | Spring Boot、DB、MQ**无** BitAnswer 客户端 native |
| **交付形态** | 多枚 **薄 JAR** + 各 OS **动态库**(与 JAR 同版本) | **单枚可执行 Fat JAR**(或容器内同等物)**每个可部署服务一枚** |
| **Spring Boot** | **不使用**(库代码,避免与宿主应用 Boot 版本冲突) | **使用**4.0. 线),仅 **启动模块** 打 Fat JAR |
| **配置** | `craftlabs-auth-config` JSON + Schema | 平台自有 `application.yml` + **PostgreSQL 15** 数据源;ORM 为 **MyBatis-Plus**JSON Schema 仅用于 **校验导出的客户端配置**(若实现) |
**硬规则**:平台进程 **不得** `System.loadLibrary("craftlabs_auth_bitanswer")`;不得在服务端 classpath 引入 `**craftlabs-auth-bitanswer`** 模块(除非未来出现「纯 Java、无 native」的只读适配且经架构评审——当前 **不存在**)。
### 5.2 命名与仓库层面的识别
- **SDK 坐标**`cn.craftlabs:craftlabs-auth-*`(artifact 前缀固定,文档与 BOM 中一眼可辨)。
- **平台坐标**:建议 `cn.craftlabs.platform:*``cn.craftlabs.delivery:*`**禁止**复用 `craftlabs-auth-*` 作为应用模块 artifactId)。
- **CI 流水线**SDK 与 **platform-*** 使用 **不同 job**,产物上传 **不同仓库路径**Maven `.../sdk/` vs `.../platform/`)。
### 5.3 单 JAR 部署 vs 分模块开发(平台 / Webhook
**诉求**:运维只下发 **一个 `app.jar`**(或镜像内 `java -jar app.jar`);研发仍按 **领域拆分模块**,边界清晰、编译快、测试可隔离。
**推荐 Maven 形态**(每个 **独立可部署后端** 各自一套父 POM,勿与 SDK 父 POM 合并):
```
delivery-platform/ # 或 license-webhook 同理
├── pom.xml # packaging=pomdependencyManagement
├── platform-domain/ # 实体、领域服务接口、无 Spring Web
├── platform-application-service/ # 用例、事务边界、调用 domain
├── platform-adapters-web/ # REST Controller、DTO 映射
├── platform-adapters-persistence/ # MyBatis-Plus Mapper / XMLPostgreSQL 15
└── platform-bootstrap/ # 唯一含 main + spring-boot-maven-plugin
└── src/main/java/.../Application.java
```
- **仅 `platform-bootstrap`** 配置 `spring-boot-maven-plugin``**repackage**`(或 `executable`),依赖其余模块为 **普通 jar**,打进 **Fat JAR / Executable JAR**
- `mvn -pl platform-bootstrap -am package` → 产出 `platform-bootstrap/target/*.jar` **一枚**,即部署单元。
- **开发体验**IDE 打开 **聚合工程**,多模块跳转、分模块单测;**禁止**在多个模块上重复声明 `repackage`(否则出现多枚「可执行」JAR 或依赖错乱)。
**可选变体**
- **分层 jarSpring Boot 2.3+**`layertools` 优化镜像层 —— 仍对应 **单次构建的一条启动入口**
- **Webhook 与 Core API 分开发布**:两个 Maven 工程各产 **一枚** Fat JAR`webhook-bootstrap.jar``platform-bootstrap.jar`),比强行塞进一个进程更符合 **扩容与故障隔离**;若强制单进程,可用 **同一 `bootstrap` 模块多 profile**(一般 **不推荐**,耦合过高)。
### 5.4 SDK 侧构建(对照:不是 Fat JAR)
- `craftlabs-auth-core` / `bitanswer` / `selfhosted` 均为 **library jar****不打** `spring-boot-maven-plugin`
- 客户端应用(客户自有 Spring Boot 或其它)自行打包 Fat JAR,**声明依赖** `craftlabs-auth-bitanswer` + 携带 **匹配版本 native**
- **平台 Fat JAR** 与 **客户应用 Fat JAR****两条完全不同的发布线**,不得在平台 POM 里 `dependency` 整条 SDK 父工程。
### 5.5 评审检查(单 JAR + 分模块)
- 全仓库是否 **只有一个** 模块声明 `spring-boot-maven-plugin``repackage`(针对该可部署应用)?
- `mvn dependency:tree -pl platform-bootstrap` 中是否 **无** `craftlabs-auth-bitanswer`
- 部署文档是否写清:启动类 FQCN、`java -jar` 与 JVM 参数、**与 SDK 版本无耦合**?
---
## 6. Native 边界
- **产出物**:各平台 `.so` / `.dylib` / `.dll` + 头文件。
- **与 Java 契约**`NativeBridge``jni_bridge.cpp` 必须 **同版本发布**
- **平台服务****不**链接本 native 库;仅 **现场 AI/业务进程** 链接。
---
## 7. 规划工程划分(推荐独立 Git 仓库)
下列目录在 `**engineering/planned/`** 中以 README 描述,**默认新开仓库**(名称可调整):
| 规划 ID | 建议仓库名 | 技术栈(与产品文档对齐) | 与本工作区关系 |
| ------------------------- | -------------------------------- | ---------------------------------------- | ------------------------------------------------- |
| `license-webhook-ingress` | `craftlabs-license-webhook`(示例) | Spring Boot **4.0.**,仅 Ingress + 验签 + MQ | **消费**比特 Callback**不**依赖 SDK native |
| `delivery-platform-api` | `craftlabs-delivery-platform` | Spring Boot **4.0.**,领域 API、**PostgreSQL 15**、**MyBatis-Plus** | 依赖 **schemas** 可通过 git submodule 或发布 **schema 包** |
| `delivery-platform-ui` | `craftlabs-delivery-platform-ui` | Vue 3 + Vite | 调用 `delivery-platform-api` |
**可选 Monorepo**:若公司策略要求单仓,可采用 **Bazel / Gradle composite / 多根 `pom` 父子不包含 SDK** 的目录隔离,例如:
```
mega-repo/
craftlabs-authorization-sdk/ # 本仓库 subtree 或 submodule
delivery-platform/
license-webhook/
```
迁移成本高于 **三仓 + 契约版本化****默认推荐三仓**。
---
## 8. CI/CD 划分建议
| 组件 | 触发路径 | 产物 |
| ----------- | ------------ | ------------------------------------- |
| SDK Java | `java/`** | Maven deploy(私服/Central |
| SDK Native | `native/**` | 各 OS 工件 + 与 Java **同版本 tag** |
| Schema | `schemas/`** | 可与 SDK 同步发 **minor**breaking 走 major |
| 平台与 Webhook | **独立仓库**(或本仓 `services/` | **Fat JAR** + 部署说明(`systemd`/脚本/`docker run`);**不强制** 容器与 K8s,规模化后再考虑镜像与编排 |
---
## 9. 数据与配置契约
- **客户端**`craftlabs-auth-config.schema.json` + 环境 `bitanswer.url` 等(见 `examples/config`)。
- **平台**:存储 SN、合同、Callback 等 —— **领域模型独立于 SDK**;仅 **映射表** 与比特特征 ID 与 M6 文档一致。
- **Webhook Payload**:以比特官方规则文档为准;平台侧 **反序列化 DTO**`license-webhook` 仓维护。
### 9.1 平台数据栈(架构锁定)
| 项 | 约定 |
|----|------|
| **数据库** | **PostgreSQL 15**(生产、验收、联调基准版本;镜像 `postgres:15` |
| **ORM** | **MyBatis-Plus**Spring Boot 3 使用 `mybatis-plus-spring-boot3-starter`;升级 Spring Boot 4 时改用官方 `mybatis-plus-spring-boot4-starter` 并回归) |
| **迁移** | Flyway 或 Liquibase(后续迭代引入;与表结构变更门禁对齐) |
| **单测** | 可不启真实 PGH2 `MODE=PostgreSQL` 或 Testcontainers `postgres:15`,以团队门禁为准 |
实现参考:`services/delivery-platform-api` 已接入 MyBatis-Plus + JDBC;本地数据库见 `services/docker-compose.yml`
---
## 10. 安全边界
> **展开**:威胁面、分组件控制、信任边界图与评审清单见 [SYSTEM_ARCHITECTURE.md §9](./SYSTEM_ARCHITECTURE.md)。
### 10.1 密钥与凭据
- **Webhook**`x-bitanswer-token` / 与比特约定的共享密钥 **仅****部署机环境变量****权限受限的本地配置文件**(如 `chmod 600`);**禁止** 写入 Git、镜像层与公开 CI 日志。
- **平台数据库**:JDBC URL、用户名、密码 **仅** 注入运行时;`docker-compose` 与示例 **不得** 作为生产凭据模板。
- **管理端**:演示账号(如 `dev`/`admin`**禁止** 用于生产;生产至少 **强口令**。团队尚小时 **不强制** 上云 KMS / Vault;规模与合规要求上来后再演进。
- **SDK 发布防篡改**:以 [SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md) 为准(官方渠道、SHA-256、建议 GPG、同 tag 发布 JAR+Native)。
### 10.2 运行时与数据面
- **传输**:对外 **仅 HTTPS**Webhook、管理 API、浏览器);比特 Callback 终端 **不接明文 HTTP**(除本机健康检查等受控例外)。
- **SDK**:配置 JSON **不落** 平台库表作为默认设计;**禁止** 在 `examples/` 与文档示例中放置真实 `bitanswer.url` 密钥或生产 SN。
- **服务端 classpath****禁止** `craftlabs-auth-bitanswer``System.loadLibrary` —— 避免将 **客户端攻击面** 引入机房。
- **日志与排障**Callback 正文、Authorization、数据库 URL **默认脱敏或不打**;生产日志外送需合规评估。
### 10.3 授权与访问控制
- 管理 API:**认证(谁)** 与 **鉴权(能否操作资源)** 分层;与 **M11** 角色矩阵一致,防范 **越权(IDOR**
- Webhook**认证先于重逻辑****幂等** 防重放污染;**Body 大小限制** 防 DoS。
### 10.4 供应链与构建
- Maven/npm **锁定版本**;力所能及时做 **依赖漏洞扫描**SCA)。
- **SDK 对外发布**:每个版本附带 **SHA-256 清单****强烈建议** **GPG 签名**`maven-gpg-plugin` 或 GitHub signed releases);详见 [SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md)。
---
## 11. 演进检查清单(架构评审用)
- 新代码是否落在正确 **仓库/目录**SDK vs 平台 vs Webhook)?
- Schema 变更是否 **双向** 更新 Java `AuthConfigs` + 文档?
- Native 与 Java **是否同 tag 发布**
- 平台是否 **误引** `craftlabs-auth-bitanswer` 到服务端 classpath
- 平台构建是否 **仅 bootstrap 模块** 产出 Fat JAR,且无重复 `repackage`
- **安全**Webhook 生产是否 **启用** 共享密钥/签名?Actuator 敏感端点是否 **已收敛**?新接口是否 **默认拒否** 并完成 **鉴权**?日志是否 **无** 明文 Token/Callback
- **安全**[SYSTEM_ARCHITECTURE §9.7](./SYSTEM_ARCHITECTURE.md) 评审项是否逐项可追溯?
- **SDK 发布**[SYSTEM_ARCHITECTURE §9.8](./SYSTEM_ARCHITECTURE.md) 是否落实 **官方渠道 + 校验和(+ 签名)+ JAR/Native 同 tag**
---
## 12. 修订记录
| 日期 | 说明 |
| ---------- | ------------------------------------------------------------------------------------------- |
| 2026-04-06 | 初版:工作区走查、目录边界、规划仓库与 CI 划分;落地 `engineering/` manifest 与 `planned/`* 占位。 |
| 2026-04-06 | 增补:**§5 客户端 SDK vs 平台端** 防混淆表;**多模块开发 + 单 Fat JAR** 的 Maven 模块划分与 `repackage` 约束;CI 坐标命名建议。 |
| 2026-04-06 | 关联:[PARALLEL_ITERATION_INDEX.md](./PARALLEL_ITERATION_INDEX.md) 与 `tracks/0103` 三轨并行执行包。 |
| 2026-04-06 | **§9.1** 锁定平台 **PostgreSQL 15** + **MyBatis-Plus**`services` 工程对齐依赖与本地 `docker-compose`。 |
| 2026-04-06 | 新增汇总文档 [SYSTEM_ARCHITECTURE.md](./SYSTEM_ARCHITECTURE.md)(系统架构图 + 全量组件清单)。 |
| 2026-04-06 | **§10** 安全边界扩写(密钥、传输、SDK、鉴权、供应链);**§11** 增补安全评审项;与安全专章 [SYSTEM_ARCHITECTURE §9](./SYSTEM_ARCHITECTURE.md) 交叉引用。 |
| 2026-04-06 | **小团队假设**:凭据以宿主机环境变量/受限文件为主,**不强制** K8s/KMS**§10.1/10.4** 与 **§11** 对齐 **SDK §9.8** 发布完整性。 |
| 2026-04-06 | **§8** CI/CD 表:平台产物以 **Fat JAR + 部署说明** 为主,**不强制** K8s。 |
+397
View File
@@ -0,0 +1,397 @@
# 迭代 I3 设计说明 — M2 合同与行项 P0、M10-F01 审计、Webhook 事件 DTO v0.1
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I3** 一致 — 平台后端 **M2 合同 + 行项**、**M10-F01 关键字段变更日志**Webhook 侧 **事件 DTO v0.1** 与平台主键/枚举对齐,便于 I5 Callback 关联。
> **分支**`develop`(本仓库为契约与 SDK 工作区;平台运行时实现可在 `delivery-platform` 仓库,路径风格须与本仓 OpenAPI 一致)。
---
## 1. 上下文与引用文档
| 文档 | 路径 | 本迭代取用要点 |
| ------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| 并行迭代索引 | [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | I3 范围:M2 + M10-F01**I3 末**同步「合同状态枚举与非法迁移码」「M10-F01 字段」。 |
| 轨道 A(后端 + Webhook | [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | I3:合同+行、状态机;审计;Webhook **事件 DTO 规范化**、与平台枚举对齐;契约:**合同/行 id** 供 Callback 关联。 |
| 产品模块与功能点 | [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) | **M2-F01F04P0**:登记编辑、状态机、标的摘要、行项;**M10-F01(P0)**:关键字段变更日志(旧值/新值/人/时间)。 |
**现有 API 路径约定**(须保持一致):`services/delivery-platform-api` 中 Controller 使用 `**@RequestMapping("/api/v1/...")`**,例如 `/api/v1/customers``/api/v1/projects`。合同相关接口统一前缀 `**/api/v1/contracts`**。
**OpenAPI 单一事实来源**:契约快照路径为仓库根下 `[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`;新增/变更接口须更新该快照,并与 `OpenApiContractSnapshotTest` 对齐。
---
## 2. 领域模型:合同(Contract
### 2.1 实体职责
合同是「卖什么」的权威来源之一,关联 M1 客户与项目;行项为履约/授权上游锚点(与后续 M3/M4 衔接)。
### 2.2 字段(P0
| 字段 | 类型 | 约束 | 说明 |
| ------------------------- | --------------- | ------- | ------------------------------------------------------------------------ |
| `id` | int64 | 主键 | 与现有 `customers`/`projects` 一致用雪花或序列,API 中为 string 或 number 以 OpenAPI 为准。 |
| `contractNumber` | string | 必填,业务唯一 | 合同编号;唯一索引。 |
| `customerId` | int64 | 必填,FK | 指向客户。 |
| `projectId` | int64 | 必填,FK | 指向项目;须属于同一 `customerId`(服务端校验)。 |
| `signedAt` | date (ISO-8601) | 必填 | 签订日。 |
| `effectiveAt` | date | 必填 | 生效日。 |
| `endAt` | date | 可选 | 结束/到期日;与「终止」语义可并存(终止优先于到期展示逻辑由前端/报表约定)。 |
| `status` | enum | 必填 | 见 §3;**API 与 JSON 仅使用英文枚举名**。 |
| `createdAt` / `updatedAt` | timestamp | 系统字段 | 审计展示可与 M10-F01 互补。 |
### 2.3 状态枚举(对齐 BPM 语义)
| BPM 语义(展示/字典) | API 枚举值 `ContractStatus` |
| ------------- | ------------------------ |
| 草稿 | `DRAFT` |
| 待生效 | `PENDING_EFFECTIVE` |
| 生效 | `EFFECTIVE` |
| 变更中 | `CHANGING` |
| 终止 | `TERMINATED` |
字典表可增加 `dict_type = CONTRACT_STATUS``code` 与上表一致,`label_zh` 为左列中文。
### 2.4 编辑规则(与状态机联动)
- **仅 `DRAFT` 状态**允许对**合同头字段**(§2.2 中除 `id``status``createdAt``updatedAt` 外,是否含 `contractNumber` 由产品确认;P0 建议 **DRAFT 下编号可改**,一旦进入 `PENDING_EFFECTIVE` 则编号只读)及**行项**做增删改。
-`DRAFT` 下对合同头或行项的 `PUT`/`POST`/`DELETE`**409 Conflict**,错误码见 §4.3。
- 状态变更**仅允许**通过 `**POST .../transition`**(§5.4),禁止在普通 `PUT` 请求体中直接改 `status`(若传入与当前相同可忽略或 400,建议 **忽略** 幂等)。
---
## 3. 领域模型:合同行(Contract Line
### 3.1 字段(P0
| 字段 | 类型 | 约束 | 说明 |
| ------------- | --------------- | ----- | ------------------------------------------------ |
| `id` | int64 | 主键 | |
| `contractId` | int64 | 必填,FK | |
| `lineNo` | int32 | 必填 | 行号,从 1 递增;**同一合同内唯一**;用于展示与排序。 |
| `skuCode` | string | 条件 | `**skuCode``productName` 至少填一个**(另一个可为 null)。 |
| `productName` | string | 条件 | 无 SKU 时的产品/包名称。 |
| `quantity` | decimal 或 int64 | 必填 | 数量;小数与否由产品线约定,P0 建议 `number` JSON。 |
| `unitPrice` | decimal | 可选 | 单价;敏感字段可按角色脱敏(见 §9)。 |
| `termNotes` | string | 可选 | 期限/席位/交付与授权口径等说明(与 M2-F03 摘要同源数据)。 |
### 3.2 排序
列表接口默认按 `lineNo` 升序;`lineNo` 可由客户端指定,冲突时 **409**,错误码建议 `CONTRACT_LINE_NO_CONFLICT`
---
## 4. 状态机
### 4.1 允许迁移(P0
以下「当前状态 → 目标状态」为 **允许**;未列出的单向迁移视为 **禁止**
| 当前状态 | 允许的目标状态 |
| ------------------- | --------------------------------------- |
| `DRAFT` | `PENDING_EFFECTIVE``TERMINATED` |
| `PENDING_EFFECTIVE` | `EFFECTIVE``DRAFT`(撤回至草稿)、`TERMINATED` |
| `EFFECTIVE` | `CHANGING``TERMINATED` |
| `CHANGING` | `EFFECTIVE``TERMINATED` |
| `TERMINATED` | (终态,不允许任何迁出) |
**说明**
- `PENDING_EFFECTIVE``DRAFT`:用于「待生效前撤回修改」;撤回后恢复 §2.4 头行可编辑。
- `CHANGING``EFFECTIVE`:变更完成、回到生效。
- P0 **不**实现变更子版本表(M2-F07 为 P1)。`**CHANGING` 下合同头与行项与普通非草稿状态相同:禁止 `PUT`/`POST`/`DELETE` 行与头**,仅允许 `POST .../transition`(例如转至 `EFFECTIVE``TERMINATED`);若业务需要「变更中改行」,留待后续迭代专用变更 API。
### 4.2 非法迁移响应
- HTTP `**409 Conflict`**。
- 响应体(与平台统一错误结构对齐;若尚无 RFC 7807,则用 JSON)示例:
```json
{
"code": "CONTRACT_ILLEGAL_STATUS_TRANSITION",
"message": "不允许从当前状态转换到目标状态",
"currentStatus": "EFFECTIVE",
"targetStatus": "DRAFT"
}
```
- `code` 固定 `**CONTRACT_ILLEGAL_STATUS_TRANSITION**`,便于前端与 SDK 分支处理。
- 日志与 M10:建议记一条 `audit_log``action = STATUS_TRANSITION_DENIED`(见 §6)。
### 4.3 非草稿编辑冲突
在不允许编辑的状态下修改头或行:
- HTTP `**409 Conflict**`
- `code`: `**CONTRACT_NOT_EDITABLE_IN_STATUS**`(或细分头/行码,P0 可合并为一个码)。
```json
{
"code": "CONTRACT_NOT_EDITABLE_IN_STATUS",
"message": "仅草稿状态可编辑合同及行项",
"status": "EFFECTIVE"
}
```
---
## 5. REST API 设计
**前缀**`/api/v1`(与 [CustomerController](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/customer/CustomerController.java) 一致)。
**认证**:与现有 `/api/v1/customers` 相同(JWT 等);未登录 **401**
**分页**:列表接口与 customers 对齐:`page`(从 0 默认)、`size`(默认 20,最大 200)。
### 5.1 合同
| 方法 | 路径 | 说明 |
| ------ | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `GET` | `/api/v1/contracts` | 分页列表;查询参数:`page``size``customerId?``projectId?``status?``keyword?`(匹配 `contractNumber`)。 |
| `POST` | `/api/v1/contracts` | 创建;**初始状态必须为 `DRAFT`**(请求体可不传 `status`,服务端默认 `DRAFT`;若传其它值 **400**)。 |
| `GET` | `/api/v1/contracts/{contractId}` | 详情;含行项可内嵌或仅用行接口拉取(P0 建议详情 **内嵌 `lines`** 减少往返)。 |
| `PUT` | `/api/v1/contracts/{contractId}` | 更新头字段;**仅 `DRAFT`****不得**携带 `status` 变更(忽略或 400,建议 **400** `CONTRACT_STATUS_USE_TRANSITION_ENDPOINT`)。 |
**创建请求示例**
```json
{
"contractNumber": "HT-2026-0001",
"customerId": 1001,
"projectId": 2002,
"signedAt": "2026-04-01",
"effectiveAt": "2026-04-15",
"endAt": "2027-04-14"
}
```
**详情响应示例(内嵌行)**
```json
{
"id": 3001,
"contractNumber": "HT-2026-0001",
"customerId": 1001,
"projectId": 2002,
"signedAt": "2026-04-01",
"effectiveAt": "2026-04-15",
"endAt": "2027-04-14",
"status": "DRAFT",
"lines": [
{
"id": 4001,
"lineNo": 1,
"skuCode": "SKU-PRO-01",
"productName": null,
"quantity": 10,
"unitPrice": 1999.00,
"termNotes": "1 年订阅,100 席位"
}
],
"createdAt": "2026-04-06T10:00:00Z",
"updatedAt": "2026-04-06T10:00:00Z"
}
```
### 5.2 合同行 CRUD
| 方法 | 路径 | 说明 |
| -------- | ----------------------------------------------- | ---------------------------- |
| `GET` | `/api/v1/contracts/{contractId}/lines` | 行列表(可选,若详情已内嵌则可与产品取舍)。 |
| `POST` | `/api/v1/contracts/{contractId}/lines` | 新增行;**仅合同为 `DRAFT`**。 |
| `GET` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 单行。 |
| `PUT` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 更新;**仅 `DRAFT`**。 |
| `DELETE` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 删除;**仅 `DRAFT`**;成功 **204**。 |
**创建/更新行请求体示例**
```json
{
"lineNo": 2,
"skuCode": null,
"productName": "企业旗舰包",
"quantity": 5,
"unitPrice": null,
"termNotes": "按项目交付"
}
```
### 5.3 状态迁移
| 方法 | 路径 | 说明 |
| ------ | ------------------------------------------- | ----------------------------------------------------------------------------- |
| `POST` | `/api/v1/contracts/{contractId}/transition` | 请求体指定目标状态;校验 §4.1;成功返回更新后的合同 DTO(或 204 + LocationP0 建议 **200 + 完整合同 JSON**)。 |
**请求体**
```json
{
"targetStatus": "PENDING_EFFECTIVE"
}
```
**成功**`200`body 为合同资源(含 `lines` 若详情惯例如此)。
---
## 6. M10-F01`audit_log` 表与读 API
### 6.1 表设计(建议名 `audit_log`
| 列名 | 类型 | 说明 |
| --------------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| `id` | bigserial PK | |
| `entity_type` | varchar(32) | 枚举:`**CUSTOMER`**、`**CONTRACT`**、`**CONTRACT_LINE**`(与产品 M10-F01「客户、合同、SN…」对齐;I3 落地三类,SN 后续迭代加 `LICENSE_SN` 等)。 |
| `entity_id` | int8 | 业务主键,与 `entity_type` 对应实体 id。 |
| `action` | varchar(64) | 如:`CREATE``UPDATE``DELETE``STATUS_TRANSITION``STATUS_TRANSITION_DENIED`。 |
| `field_name` | varchar(128) | 可选;字段级变更时记录英文名,如 `effectiveAt``status`。 |
| `old_value` | text | JSON 字符串;无则 NULL。 |
| `new_value` | text | JSON 字符串;无则 NULL。 |
| `actor_user_id` | int8 | 操作人用户 id。 |
| `created_at` | timestamptz | 不可改。 |
**索引**
- `(entity_type, entity_id, created_at DESC)` — 按对象拉时间线。
- 可选:`(actor_user_id, created_at DESC)` — 按人审计(M10-F02 预备)。
**写入时机(I3 最小集)**
- 合同:创建、头字段更新(`DRAFT`)、每次成功 `transition``field_name=status`,old/new 为枚举字符串)。
- 合同行:创建、更新、删除(`entity_type=CONTRACT_LINE``entity_id=lineId`)。
- 拒绝的非法迁移:可选记 `STATUS_TRANSITION_DENIED``new_value` 可存目标状态 JSON。
### 6.2 读 API
| 方法 | 路径 | 说明 |
| ----- | --------------- | ------- |
| `GET` | `/api/v1/audit` | 分页审计列表。 |
**查询参数(组合过滤)**
- `entityType` + `entityId`:精确到单一实体(如某合同、某行、某客户)。
- `**contractId`**:便捷范围 — 返回 `entity_type IN ('CONTRACT','CONTRACT_LINE')` 且(合同 id = contractId **或** 行所属 contractId = contractId)的记录;实现上可用 SQL `UNION` 或冗余 `contract_id` 列(**推荐冗余 `contract_id` 可空** 于 `audit_log` 以简化查询:合同与行写入时均填 `contract_id`,客户实体则只填 `entity_`*)。
**冗余列(可选但强烈推荐)**
| 列名 | 说明 |
| ------------- | ---------------------------------------------------------------------- |
| `contract_id` | 可空;`CONTRACT` 时等于 `entity_id``CONTRACT_LINE` 时为父合同 id`CUSTOMER` 可为空。 |
**响应项示例**
```json
{
"id": 900001,
"entityType": "CONTRACT",
"entityId": 3001,
"contractId": 3001,
"action": "STATUS_TRANSITION",
"fieldName": "status",
"oldValue": "\"DRAFT\"",
"newValue": "\"PENDING_EFFECTIVE\"",
"actorUserId": 42,
"createdAt": "2026-04-06T12:00:00Z"
}
```
说明:`old_value`/`new_value`**JSON text**(字符串加引号、对象则序列化),解析由前端或工具完成。
---
## 7. Webhook 事件 DTO v0.1Callback 关联预备)
**目标**:与平台合同/行主键对齐,便于 I5 Inbox 关联与幂等;**不**在本迭代要求完整比特 payload,仅 **最小信封**
### 7.1 建议信封字段(v0.1
| 字段 | 类型 | 必填 | 说明 |
| --------------- | ----------------- | --- | ----------------------------------------------- |
| `schemaVersion` | string | 是 | 固定 `**0.1`**(后续 `0.2``1.0` 递增)。 |
| `contractId` | int64 | 条件 | 与平台合同 id 一致;若事件仅到行级,仍建议带父 `contractId`。 |
| `lineIds` | array of int64 | 否 | 涉及的合同行 id 列表;无行级时可 `[]` 或省略。 |
| `eventType` | string | 否 | 预留,如 `contract.status.changed`(与 M5 字典统一可在 I5)。 |
| `occurredAt` | string (ISO-8601) | 否 | 事件发生时间。 |
**示例**
```json
{
"schemaVersion": "0.1",
"contractId": 3001,
"lineIds": [4001, 4002],
"eventType": "contract.status.changed",
"occurredAt": "2026-04-06T12:00:00Z"
}
```
**版本策略**Webhook 与平台共用 `schemaVersion` 语义;**I3 归档** JSON Schema 或本仓库 `contracts/` 下示例文件(与 [轨道 A 文档 §3](../tracks/01-backend-platform-webhook.md) 的 `schemaVersion` / `X-Event-Schema-Version` 一致)。
---
## 8. 安全与 RBAC(对齐产品粗粒度矩阵)
产品文档 [§13.3](../../chuangfei-platform-product-modules.md) 模块矩阵中 **M2 合同**、**M10 审计** 与角色关系如下(**R** 查看,**W** 新建编辑,**X** 导出)。用户提到的 `**DEVELOPER`** 在产品预置角色中为 `**DEV_SUPPORT`(研发/集成支撑)** — 实施时角色码二选一须与 IAM 统一,下文按矩阵描述 `**DEV_SUPPORT`**,若代码命名为 `DEVELOPER` 则与之对齐。
### 8.1 `SYS_ADMIN`
- **M2 合同**:矩阵为 **M / RWDX** — 含模块管理语义;企业可配置是否开放业务写。建议:**生产默认** `SYS_ADMIN` **仅 M11 管理面**,业务合同与 `**SALES`/`ORDER_SUPPORT`** 同权或按企业策略单独开 `contract:`* 权限码。
- **M10 审计**:矩阵为 **M**(管理面);若启用审计检索,建议单独挂 `audit:search`
### 8.2 `DEV_SUPPORT`(研发支撑 / 可与 `DEVELOPER` 对齐)
- **M2 合同**:**R**(只读)— 无合同创建/编辑/删除/导出,除非临时提权。
- **M10 审计**:**R** — 可检索审计(与矩阵「研发支撑」列一致)。
### 8.3 I3 API 权限码映射(建议)
| 接口 | 建议权限码 | 典型角色 |
| ---------------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
| 合同与行 CRUD、`transition` | `contract:order:rw` | `SALES``ORDER_SUPPORT``SYS_ADMIN`(若开放业务写) |
| 合同只读列表/详情 | `contract:order:rw` 或细拆 `contract:order:read`Mid | 上表 + `DELIVERY``LICENSE_OPS``FINANCE_VIEW``EXEC_VIEW` 等只读列 |
| `GET /api/v1/audit` | `audit:search` | `COMPLIANCE``DEV_SUPPORT``FINANCE_VIEW`(导出另加 `audit:export` P1 |
**说明**:粗粒度阶段可将「读合同」与「写合同」合并为同一码,但 `**transition`** 必须与写权限同级或单独 `contract:order:transition`,避免只读角色误调;P0 可与 `contract:order:rw` 绑定。
---
## 9. OpenAPI 与交付物
- **快照路径**`[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`
- I3 完成定义:**上述路径、枚举、主要 DTO** 均须出现在该 OpenAPI 文件中,并与 `services/delivery-platform-api` 运行时 `/v3/api-docs``**OpenApiContractSnapshotTest`** 校验一致。
---
## 10. 修订记录
| 日期 | 说明 |
| ---------- | ---------------------------------------------------------- |
| 2026-04-06 | 初版:I3 合同/行项、状态机、REST、M10-F01、Webhook v0.1、RBAC、OpenAPI 引用。 |
+299
View File
@@ -0,0 +1,299 @@
# 迭代 I4 设计说明 — M3 交付批次与清单、M4 许可 SN 台账
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I4** 一致 — 平台后端 **M3 交付** + **M4 SN 录入/绑定/状态/手工回写**;前端 **交付页 + SN 页**;本仓库(SDK 工作区)以 **OpenAPI 契约与文档口径** 与 BP-10 对齐。
> **分支**`develop`。
> **已有实现锚点**(勿从零重设计,仅对齐与补全):Flyway `V4__delivery_batch_and_license_sn.sql``cn.craftlabs.platform.api.domain.DeliveryBatchStatus` / `LicenseSnStatus``web/dto` 下 `Delivery*`、`LicenseSn*`;审计常量 `AuditEntityTypes`、`AuditActions` 已含 `DELIVERY_BATCH`、`LICENSE_SN` 及对应动作。
---
## 1. I4 范围与 I3 / I5 边界
### 1.1 I4 **纳入**(本迭代 DoD
| 域 | 说明 |
|----|------|
| **M3 P0** | 交付批次(项目/可选合同、批次号、计划日、备注);交付清单行(描述、数量、可选合同行关联);批次状态 **PENDING → DELIVERED / CANCELLED** 及完成时间等侧写。对应产品:[M3-F01F05 P0](../../chuangfei-platform-product-modules.md#4-m3-交付管理)。 |
| **M4 P0** | SN 台账:全局唯一 `sn_code`**`project_id` 与/或 `contract_line_id` 绑定路径**;生命周期状态子集;激活备注/手工回写字段。对应产品:[M4-F01F05 P0](../../chuangfei-platform-product-modules.md#5-m4-授权与许可运营)。 |
| **M10-F01** | 交付批次、交付行、SN 的关键变更与状态迁移写入审计(与 I3 合同审计模式一致;实体类型见 §4)。 |
| **跨轨口径** | [I4 末同步点](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐):**SN 绑定与「孤儿 SN」规则**文档化并三轨对齐;**交付门禁(M3-F07)与「孤儿 SN」强校验(M4-F02)** 在 **M11-F20 系统参数** 中预留为 **未来可配置项**(I4 可实现默认策略 + 配置占位,**不阻塞** I4 闭环)。 |
### 1.2 I3 **留给上游的契约**(I4 只消费,不重复建设)
- **合同 / 合同行**`project_id``contract_id`、行项主键;合同状态机已在 I3 冻结。交付行上的 `contract_line_id` 必须解析到合法合同行及其所属项目。
- **客户 / 项目**:批次必填 `project_id`;可选 `contract_id` 须属于同一项目。
### 1.3 I5 **明确不纳入 I4**(避免范围蔓延)
- **M5 Callback Inbox**、**M6 集成配置** 的持久化与页面(I5 起)。
- Webhook **生产级** 投递、幂等落库与平台 Inbox 全链路 E2E。
- **设备(M7)**、**比特控制台摘要链接(M4-F06)** 等可后续挂接;I4 仅保证 SN 主数据与绑定字段可关联到合同行/项目。
---
## 2. 数据模型锚点(与迁移一致)
表与字段以 `services/delivery-platform-api/src/main/resources/db/migration/V4__delivery_batch_and_license_sn.sql` 为准:
- **`platform_delivery_batch`**`project_id`(必填)、`contract_id`(可选)、`batch_code`(唯一)、`planned_delivery_date``status`(默认 `PENDING`)、`finished_at``remarks`
- **`platform_delivery_line`**:归属 `batch_id``description``quantity``contract_line_id`(可选),`sort_order`
- **`platform_license_sn`**`sn_code`(全局唯一)、`project_id` / `contract_line_id`(均可空于 DB 层,**业务校验见 §4**)、`status`(默认 `REGISTERED`)、`activation_remark`
### 2.1 状态枚举(API JSON 使用枚举名字符串)
**交付批次** `DeliveryBatchStatus`
| 值 | 说明 |
|----|------|
| `PENDING` | 未交付(默认) |
| `DELIVERED` | 已交付 |
| `CANCELLED` | 已取消 |
**许可 SN** `LicenseSnStatus`P0 子集,与代码枚举一致):
| 值 | 说明 |
|----|------|
| `REGISTERED` | 已登记 |
| `ISSUED` | 已发放 |
| `ACTIVATED` | 已激活 |
| `SUSPENDED` | 已冻结 |
| `REVOKED` | 已回收 |
非法状态迁移返回 **409**,错误码建议与合同类似:`DELIVERY_BATCH_ILLEGAL_STATUS``LICENSE_SN_ILLEGAL_STATUS`(具体以 OpenAPI 与实现为准)。
---
## 3. REST API 提案(前缀 `/api/v1`
与现有 Controller 风格一致:**`@RequestMapping("/api/v1/...")`**。下列路径为 I4 计划形态;JSON 字段名与当前 DTO **camelCase** 对齐(`projectId``contractId``batchCode` 等)。
### 3.1 交付批次 `delivery-batches`
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/delivery-batches` | 分页列表;查询参数建议:`projectId``contractId``status``keyword`(批次号)、`page``size`。 |
| `POST` | `/api/v1/delivery-batches` | 创建批次(体见下);**不含**行时可后续用行接口追加。 |
| `GET` | `/api/v1/delivery-batches/{id}` | 详情;可通过 `?includeLines=true` 或默认嵌套返回 `lines`(与 `DeliveryBatchResponse` 一致)。 |
| `PUT` | `/api/v1/delivery-batches/{id}` | 更新计划交付日、备注等非状态字段(`DeliveryBatchUpdateRequest`)。 |
| `PATCH` | `/api/v1/delivery-batches/{id}/status` | **仅**变更状态:`PENDING``DELIVERED``CANCELLED`;服务端可在此写入 `finishedAt`(如 `DELIVERED`)。 |
**嵌套 — 交付行 `lines`**
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/delivery-batches/{batchId}/lines` | 清单列表。 |
| `POST` | `/api/v1/delivery-batches/{batchId}/lines` | 新增一行。 |
| `PUT` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 更新行。 |
| `DELETE` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 删除行。 |
**创建批次请求体示例**
```json
{
"projectId": 1001,
"contractId": 2002,
"batchCode": "DLV-2026-0001",
"plannedDeliveryDate": "2026-04-15",
"remarks": "首批现场交付"
}
```
**更新批次请求体示例**`PUT /api/v1/delivery-batches/{id}`
```json
{
"plannedDeliveryDate": "2026-04-20",
"remarks": "延期一周"
}
```
**状态 PATCH 示例**`PATCH /api/v1/delivery-batches/{id}/status`
```json
{
"status": "DELIVERED"
}
```
**交付行写入示例**`POST` / `PUT` body`DeliveryLineRequest`
```json
{
"sortOrder": 1,
"description": "AI 推理节点 × 生产环境",
"quantity": 2,
"contractLineId": 3003
}
```
**详情响应片段**`DeliveryBatchResponse`,含行)
```json
{
"id": 1,
"projectId": 1001,
"contractId": 2002,
"batchCode": "DLV-2026-0001",
"plannedDeliveryDate": "2026-04-15",
"status": "PENDING",
"finishedAt": null,
"remarks": "首批现场交付",
"createdAt": "2026-04-06T08:00:00Z",
"updatedAt": "2026-04-06T08:00:00Z",
"lines": [
{
"id": 10,
"batchId": 1,
"sortOrder": 1,
"description": "AI 推理节点 × 生产环境",
"quantity": 2,
"contractLineId": 3003,
"createdAt": "2026-04-06T08:05:00Z",
"updatedAt": "2026-04-06T08:05:00Z"
}
]
}
```
### 3.2 许可 SN `license-sns`
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/license-sns` | 分页列表;建议查询:`projectId``contractLineId``status``snCode`(精确或前缀按产品定)。 |
| `POST` | `/api/v1/license-sns` | 创建 SN`LicenseSnCreateRequest`);须满足 §4 绑定规则。 |
| `GET` | `/api/v1/license-sns/{id}` | 详情。 |
| `PUT` | `/api/v1/license-sns/{id}` | **全量/部分更新绑定字段**`projectId``contractLineId``activationRemark``LicenseSnUpdateRequest`);用于纠正绑定或手工回写备注。 |
| `PATCH` | `/api/v1/license-sns/{id}/status` | 变更 `LicenseSnStatus``LicenseSnStatusPatchRequest`);须校验合法迁移。 |
**创建 SN 请求体示例**
```json
{
"snCode": "SN-CRAFT-8F3A-0001",
"projectId": 1001,
"contractLineId": 3003,
"activationRemark": null
}
```
**更新绑定 / 备注示例**`PUT`
```json
{
"projectId": 1001,
"contractLineId": 3003,
"activationRemark": "客户现场激活成功,凭证号 xxx"
}
```
**状态 PATCH 示例**
```json
{
"status": "ACTIVATED"
}
```
**详情响应示例**`LicenseSnResponse`
```json
{
"id": 501,
"snCode": "SN-CRAFT-8F3A-0001",
"projectId": 1001,
"contractLineId": 3003,
"status": "ACTIVATED",
"activationRemark": "客户现场激活成功,凭证号 xxx",
"createdAt": "2026-04-06T09:00:00Z",
"updatedAt": "2026-04-06T10:00:00Z"
}
```
---
## 4. 校验规则与审计
### 4.1 交付批次
- **`projectId`**:必填;项目须存在。
- **`contractId`**:可选;若提供,合同须存在且 **`contract.project_id == batch.project_id`**。
- **`batchCode`**:全平台唯一(与表 `uq_platform_delivery_batch_code` 一致);冲突 **409**
- **状态**:仅允许自 `PENDING` 转至 `DELIVERED``CANCELLED``DELIVERED`/`CANCELLED` 视为终态,**禁止**再次变更(除非产品后续另定「重开」流程,不在 I4 P0)。
### 4.2 交付行
- **`contractLineId`**:可选;若提供,合同行须存在,且其所属合同的 **`project_id` 须与父批次 `project_id` 一致**(从而与批次可选 `contract_id` 兼容:若批次已指定合同,可额外校验行所属合同与批次合同一致,建议 **强一致**:行上合同行必须属于 `batch.contract_id``contract_id` 非空时)。
- **`quantity`**> 0(与 `DeliveryLineRequest``@DecimalMin` 一致)。
### 4.3 许可 SN
- **`snCode`**:必填;**全局唯一**;冲突 **409**
- **绑定路径****至少具备 `projectId``contractLineId` 之一**(可同时具备)。
- 若仅提供 **`contractLineId`**:服务端**派生** `project_id` = 该合同行所属合同的 `project_id`,并持久化(便于列表按项目过滤)。
- 若同时提供两者:须校验 **`contractLine` 派生出的 `project_id` 与请求 `projectId` 一致**,否则 **400**
- **孤儿 SN(与 I4 末同步对齐)**:
- **产品理想态(M4-F02)**:禁止无项目且无合同行路径的「裸 SN」。
- **M11-F20P1**:「孤儿 SN」**强校验**开关、**交付门禁**(M3-F07,例如仅已交付范围可发放/绑定)作为**系统参数**在架构上预留;I4 建议 **默认策略**:创建/更新时 **拒绝** 零绑定(与 P0 一致);若需「先录入后绑定」,可通过 **配置** 降级为 **警告 + 允许保存**(实现可放在应用服务层读取参数表,**表结构可 Mid 再做**,I4 先在文档与 OpenAPI `description` 中固定语义)。
- **状态迁移**:按 §2.1 枚举定义允许边(细表可在实现中维护;**禁止**随意跳转到任意状态)。
### 4.4 审计(M10-F01
沿用 I3 模式:**实体类型 + 动作 + 旧值/新值摘要 + 操作者 + 时间**。常量已存在于:
- `AuditEntityTypes.DELIVERY_BATCH``AuditEntityTypes.LICENSE_SN`
- `AuditActions``DELIVERY_BATCH_CREATED` / `UPDATED` / `STATUS_CHANGED``DELIVERY_LINE_ADDED` / `UPDATED` / `DELETED``LICENSE_SN_CREATED` / `UPDATED` / `STATUS_CHANGED`
若持久化审计行需扩展子类型或 payload 结构,**保持与合同审计同一表结构**,仅扩展 `entity_type` / `action` 枚举值;**无需**为 I4 另起实体类型常量,除非后续拆分「交付行」为独立可检索实体(当前可用 `DELIVERY_LINE_*` 动作挂 `entity_id` = line id`batch_id` 放上下文 JSON)。
---
## 5. 前端路由(Vue 3,布局子路由)
与 [轨道 B — I4](../tracks/02-frontend-platform-ui.md) 一致,路径挂在 **AppLayout** 之下(懒加载、`meta.title` / 权限码略)。
| 路由 | 页面职责 |
|------|----------|
| `/deliveries` | 交付批次列表、筛选、跳转新建/详情。 |
| `/deliveries/new` | 新建批次(项目/可选合同、批次号、计划日、备注);可内嵌或分步添加行。 |
| `/deliveries/:id` | 批次详情:行清单 CRUD;**状态按钮** 调用 `PATCH .../status`PENDING → DELIVERED/CANCELLED)。 |
| `/licenses/sn` | SN 台账列表;**孤儿 SN** 列表筛选或醒目标记(与后端配置/字段一致)。 |
| `/licenses/sn/new` | 新建 SN(录入 `snCode`、绑定项目/合同行)。 |
| `/licenses/sn/:id` | SN 详情:**PUT** 调整绑定与 `activationRemark`;**PATCH** 调整状态;展示简要时间线(可仅读审计或本地状态历史 Mid 增强)。 |
**契约顺序提醒**[轨道 B §4](../tracks/02-frontend-platform-ui.md)):Auth → Customer/Project → Contract → **Delivery/SN** → CallbackI4 页面依赖 I2/I3 主数据与合同行选择器。
---
## 6. I4 末同步点 Checklist(后端 + 前端 + SDK 文档)
以下为 [并行索引 I4 末](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐) 的落地核对项。
### 6.1 后端(platform-api
- [ ] Flyway V4 表与索引已在各环境应用;DTO 与 OpenAPI 字段一致。
- [ ] §3 路径已实现或通过兼容别名暴露;错误码与 409 语义与合同模块一致。
- [ ] §4.1~§4.3 校验全覆盖(含合同行与项目一致性、SN 全局唯一、绑定派生)。
- [ ] **孤儿 SN**:默认策略与(可选)M11-F20 配置占位行为**文档化并在代码注释或配置类中可定位**。
- [ ] **交付门禁(M3-F07**:与 SN 创建/绑定相关的规则在代码中**可插拔**或明确「I4 硬编码默认 + I5+ 读配置」的 TODO 与 Owner。
- [ ] 审计:`DELIVERY_BATCH` / `LICENSE_SN` / 交付行动作写入 M10-F01 存储。
- [ ] `contracts/openapi/delivery-platform-api.json` 更新并通过 `OpenApiContractSnapshotTest`
### 6.2 前端(delivery-platform-ui
- [ ] §5 路由与菜单可达;RBAC 权限码与后端对齐。
- [ ] 交付详情状态操作仅展示合法迁移;错误态与 409 提示一致。
- [ ] SN 新建/编辑:合同行选择器与项目联动;**孤儿**场景 UI 与后端策略一致(禁止或警告)。
- [ ] E2E P0`交付 → SN 录入/绑定 → 状态/备注回写`(与轨道 B I4 DoD 一致)。
### 6.3 客户端 SDK 工作区(本仓库)
- [ ] OpenAPI 快照与 [contracts/README.md](../../contracts/README.md) 说明含 Delivery/SN 标签。
- [ ] [tracks/03-client-sdk.md](../tracks/03-client-sdk.md) 或等价文档中 **BP-10 与平台对象口径** 补充:交付批次、SN 在集成叙事中的位置(**不实现**平台 REST 客户端亦可,但**文档**须与 I4 契约一致)。
- [ ] **M11-F20**:在 SDK/集成文档中标注为 **后续配置项**(门禁、孤儿强校验),避免集成方误假设 I4 已暴露该 HTTP API。
---
## 7. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I4 范围与边界、REST 提案、校验与审计、前端路由、I4 末三轨 checklist。 |
+229
View File
@@ -0,0 +1,229 @@
# 迭代 I5 / I6 设计说明 — M5 Callback Inbox、M6 集成最小面、Webhook 生产链与 UAT 冻结
> **仓库**`craftlabs-authorization-sdk`(分支 `develop`)。
> **角色**:解决方案架构设计稿;**不**在本任务中落地代码。
> **实现锚点**(与现有模式一致):`delivery-platform-api` 使用 Flyway `V5__…` 起(当前末版为 `V4__delivery_batch_and_license_sn.sql`)、`AuditService` + `AuditEntityTypes` / `AuditActions`、`ApiExceptionHandler`、公开业务 API 经 `SecurityConfig` + `JwtAuthenticationFilter``Authorization: Bearer`);OpenAPI SSOT 为 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json) 与 `OpenApiContractSnapshotTest`。
> **Webhook 工程名**:本工作区已实现为 `license-webhook-ingress`[`services/README.md`](../../../services/README.md)`webhook_callback_receipt`、`Idempotency-Key`)。**I7**:平台 HTTP 投递经 `webhook_platform_delivery` 异步出库,详见 [I7_DESIGN.md](./I7_DESIGN.md)。
---
## 0. 上游文档走查(按路径引用,不全文摘录)
| 文档 | 与本设计的关系(摘要) |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | 定义三轨并行;**I5** 为 **Callback + Schema** 硬耦合周;**I6** 为 **UAT** 与 SDK 冻结;**I7** 起 Callback 权限与异步投递见索引表。同步点含 **I5 起**Callback payload / inbox DTO、**Idempotency-Key**、Webhook→平台投递方式;**I6**UAT 场景、`VITE_API_BASE`、两枚 Fat JAR 与 SDK 版本。 |
| [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | **I5 DoD**E2E「模拟 Callback → 平台 DB 一条 Inbox」;**§3 Webhook↔平台**`schemaVersion` / `X-Event-Schema-Version`、幂等 `(source_system, external_message_id)``POST /internal/v1/callback-events` 或 MQ、对比特 **2xx** 须在持久化或可靠入队**之后**。 |
| [docs/engineering/tracks/02-frontend-platform-ui.md](../tracks/02-frontend-platform-ui.md) | **I5 路由**`/callbacks``/integration/environments``product-lines`(本文统一为 `/integration/product-lines`);组件:`CallbackInboxTable``CallbackPayloadViewer`(脱敏);与 Webhook 联调或 staging。 |
| [docs/engineering/tracks/03-client-sdk.md](../tracks/03-client-sdk.md) | **I5****Schema + `AuthConfigs` + examples** 与 **BP-10** 同步为硬交付;**I6**:冻结 SDK 版本、CHANGELOG、**BitAnswer 兼容矩阵**UAT 周禁止 MAJOR Schema。 |
| [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) §67 | **M5 P0**:收件箱列表/详情/处理状态/关联失败兜底/事件类型字典(M5-F01~F05 等);**M6 P0**:产品线(M6-F01)、环境维度与 `bitanswer.url`M6-F02);P1 如比特 ID 映射、JSON 模板、发布记录等 **I5 MVP 可裁减**。 |
| [docs/chuangfei-platform-bpm-and-roadmap.md](../../chuangfei-platform-bpm-and-roadmap.md) | **BP-06**:Webhook 验签 → 落库 → 解析关联 → Ops 处置;重复投递幂等。**BP-10**:M6 与 Schema、客户端配置发布链路;**依赖**本仓 `schemas/craftlabs-auth-config.schema.json`。 |
---
## Part A — I5:本单体工作区内的 MVP 切片
### A.1 问题与目标结果
| 维度 | 说明 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **业务问题** | 比特规则 **HTTPS Callback****不断链、可审计、可运营处置**;平台侧需统一收件箱,避免只在 Webhook 边缘落库不可见。 |
| **I5 目标结果(E2E** | **模拟或真实 Callback**`license-webhook-ingress` →(持久化收据后)**投递** → `delivery-platform-api` **Inbox 表出现一行**;运营账号用 JWT 在 UI **列表可见、详情可打开**。 |
| **幂等** | 与轨道 A 一致:**`Idempotency-Key`**HTTP 头)+ 比特稳定 **`message_id`**(或等价字段);DB **唯一约束 `(source_system, external_message_id)`**;重复请求 **不重复插入**、返回与首次一致的接受语义(HTTP 200 + 相同 `inboxId` 或约定 DTO)。 |
| **schemaVersion** | 事件体或头携带 **`schemaVersion`**(与轨道 A 的 `X-Event-Schema-Version` 二选一或并存,**须写 ADR 定一种主口径**);平台拒绝无法识别的 major 版本时返回 **4xx** 并记录可观测字段,避免静默损坏。 |
### A.2 数据模型(`delivery-platform-api`PostgreSQL + Flyway
命名与现有表一致采用 **`platform_*` 前缀**(见 `V1``V4` 迁移)。
#### A.2.1 `platform_callback_inbox`M5 Inbox P0
| 列(示例) | 类型/说明 |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `id` | UUID / BIGSERIAL PK |
| `source_system` | VARCHAR,如 `BITANSWER` |
| `external_message_id` | VARCHAR,比特侧稳定消息 ID |
| **唯一约束** | `UNIQUE (source_system, external_message_id)` |
| `schema_version` | VARCHAR,与 payload 解析版本对齐 |
| `event_type` | VARCHAR,与 M5-F05 字典一致(如 `sn:pre_activate`) |
| `status` | ENUM/VARCHAR**`PENDING` / `PROCESSED` / `FAILED` / `IGNORED`**(对应产品「待处理、已处理、失败、忽略」) |
| `raw_payload` | **JSONB**(或 TEXT + 大小上限);UI **脱敏展示** |
| `idempotency_key` | VARCHAR NULL,审计与排障 |
| **关联(均可 NULL,支撑 M5-F04** | `license_sn_id``platform_license_sn``contract_id` / `project_id`(若已有表);解析字段如 `sn_code``mid` 等冗余列便于列表筛选 |
| `product_line_id` / `integration_environment_id` | FK → M6 最小表(可选,便于按产品线/环境筛选) |
| `received_at` | 平台收件时间 |
| `processed_at` / `processed_by_user_id` | 运营处置 |
| `failure_reason` / `operator_note` | 文本,P1 可扩展「失败原因分类」字典 |
| 标准审计 | 与现有实体一致可补充 `created_at`/`updated_at`;关键状态迁移建议走 **`AuditService`**(扩展 `AuditEntityTypes` / `AuditActions` |
#### A.2.2 M6 最小只读支撑表
仅包含 **I5 UI 与 Inbox 筛选** 所需字段;**不做** M6-F03F06 全量。
| 表 | P0 字段(示例) |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **`platform_product_line`** | `id``code`(唯一)、`name``description` NULL、启用标志 |
| **`platform_integration_environment`** | `id``code`(唯一)、`name``bitanswer_base_url`(对应 M6-F02)、`kind`DEV/TEST/STAGING/PROD 等枚举)、可选 `product_line_id` 或后续多对多(MVP 可 **单列 FK** 简化) |
**MVP 裁减**:比特产品/模版/业务 ID 映射(M6-F03)、特征映射(M6-F04)、JSON 模板与发布记录(M6-F05/F06**推迟**至 V1.1 或 Mid,除非比特联调硬依赖。
### A.3 公开 REST APIJWT`/api/v1`
与现有 Controller 风格一致。**RBAC(以代码为准,I7 已落地)**:
- **Callback Inbox**`GET/PATCH /api/v1/callback-inbox*`):**`OPS`** + **`SYS_ADMIN`****`DEVELOPER`** 无);见 [`CallbackInboxController`](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java)。
- **M6 只读**`/api/v1/integration/*`):**`OPS`** + **`SYS_ADMIN`** + **`DEVELOPER`**。
- **其余 I2I4 等业务 MVP**:仍为 **`SYS_ADMIN`** / **`DEVELOPER`** 演示账号模型;[`AuthController`](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java) 含 `ops/ops`
| 方法 | 路径 | 说明 |
| ------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `GET` | `/api/v1/callback-inbox` | 分页;查询建议:`status``eventType``snCode``projectId``productLineId``environmentId``from`/`to``receivedAt`)、`page``size` |
| `GET` | `/api/v1/callback-inbox/{id}` | 详情;含 `rawPayload`(或单独 `GET .../payload` 若需权限分级) |
| `PATCH` | `/api/v1/callback-inbox/{id}/status` | 运营状态迁移:**`PENDING``PROCESSED` / `FAILED` / `IGNORED`**;非法迁移 **409** + 业务错误码(与合同/交付模式一致,经 **`ApiExceptionHandler`** |
| `PATCH` | `/api/v1/callback-inbox/{id}/link`(可选) | 人工挂接:`licenseSnId` / `projectId` / `contractId` 等,支撑 M5-F04 |
**可选**`POST /api/v1/callback-inbox/simulate`**仅非生产**,对应 M5-F10 P2 — **I5 若排期紧可不做**,改用 curl → Webhook → 平台链)。
**M6 只读**
| 方法 | 路径 | 说明 |
| ----- | ---------------------------------------------------------------- | ------ |
| `GET` | `/api/v1/integration/environments` | 列表/分页 |
| `GET` | `/api/v1/integration/product-lines` | 列表/分页 |
| `GET` | `/api/v1/integration/environments/{id}``.../product-lines/{id}` | 详情(按需) |
写接口(维护环境/产品线)MVP 可 **仅种子数据 + Flyway****管理员 POST**(若 I5 周可交付则加 `POST/PUT`,否则 **推迟**)。
### A.4 内部 API(平台服务间,`license-webhook-ingress` → `delivery-platform-api`
| 项 | 说明 |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **路径** | `POST /internal/v1/callback-events` |
| **认证(MVP 推荐)** | 共享密钥:**`X-Platform-Internal-Token`**(或 `Authorization: Bearer <internal>`),配置与 `PLATFORM_JWT_SECRET` 分离;**生产建议路线图**:mTLS 或双向签名(文档中注明 **I6 Runbook:轮换步骤**)。 |
| **幂等** | 请求头 **`Idempotency-Key`** + 体 **`sourceSystem` + `externalMessageId`** 与 Inbox 唯一键一致;重复 POST → **200** 且 body 指向同一 `inboxId`。 |
| **行为** | 校验 `schemaVersion` → 插入或跳过(幂等)→ 尝试解析并 **填充关联列**(失败则 `status=PENDING` 保留人工挂接)。 |
**请求 / 响应 JSON 示例(示意)**
```http
POST /internal/v1/callback-events
Idempotency-Key: wh_01JABC...
X-Platform-Internal-Token: <redacted>
Content-Type: application/json
```
```json
{
"schemaVersion": "1.0",
"sourceSystem": "BITANSWER",
"externalMessageId": "msg_01JABC",
"eventType": "sn:post_activate",
"receivedAt": "2026-04-06T12:00:00Z",
"rawPayload": { "sn": "SN-001", "mid": "..." },
"webhookReceiptId": "optional-uuid-from-webhook-db"
}
```
```json
{
"inboxId": "550e8400-e29b-41d4-a716-446655440000",
"duplicate": false
}
```
### A.5 `license-webhook-ingress` 链路
| 步骤 | 说明 |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 1 | **验签 / token**(现有 `x-bitanswer-token``CRAFTLABS_WEBHOOK_EXPECTED_TOKEN`) |
| 2 | **持久化收据**(现有 **`webhook_callback_receipt`** + **`Idempotency-Key`** 幂等) |
| 3 | **平台投递(I7**:首单收据后写入 **`webhook_platform_delivery`**`PENDING`),由调度器 **`POST /internal/v1/callback-events`**(退避重试、`SENT`/`DEAD`);贯通 **`traceparent` / `X-Request-Id`**(轨道 A §3 |
| **对比特的 HTTP 响应** | 与轨道 A 一致:**2xx 须在收据已持久化(或可靠入队)之后**再返回。**实现**:收据落库 + 出站行入队后对比特 **2xx**,平台 HTTP 在后台执行。 |
| **平台非 2xx** | 出库任务重试;超限 **`DEAD`**(见 [RUNBOOK §10.5](../../../services/RUNBOOK.md))。**不**因平台暂时不可用而对已持久化收据重复向比特报错(若已 2xx)。 |
### A.6 OpenAPI 与 springdoc
| 项 | 说明 |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SSOT** | 更新 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json):仅 **公开** `/api/v1/callback-inbox*``/api/v1/integration/*` 路径、`components/schemas`、错误码与 `security`Bearer JWT)。 |
| **内部路由** | **`/internal/**` 建议排除在默认 springdoc 分组之外**,或打上 tag **`internal`** 且 **生产禁用**该分组(与「对外契约」分离,避免集成方误用)。 |
| **校验** | `UPDATE_OPENAPI=1 mvn test -Dtest=OpenApiContractSnapshotTest`(见 [`contracts/README.md`](../../../contracts/README.md))。 |
### A.7 前端(`web/delivery-platform-ui`
| 路由 | 页面职责 |
| ---------------------------- | ---------------------------------------------------- |
| `/callbacks` | Inbox 列表、`CallbackInboxTable`;跳转详情 |
| `/callbacks/:id` | 详情 + **`CallbackPayloadViewer`(脱敏)**;状态 PATCH、可选人工挂接 |
| `/integration/environments` | M6 环境只读表 |
| `/integration/product-lines` | 产品线只读表 |
路由 meta**I7** 与后端一致——Callback 仅 **`SYS_ADMIN`/`OPS`**;集成页含 **`DEVELOPER`**;菜单与首页按角色裁剪(见 [轨道 B I7](../tracks/02-frontend-platform-ui.md))。
### A.8 SDK 轨道(本仓库,I5 硬交付清单)
- **Schema**`schemas/craftlabs-auth-config.schema.json` 等 — 若 BP-10 变更类型触及「平台导出 → Schema → 客户端」,按 [轨道 C §3](../tracks/03-client-sdk.md) bump 规则执行。
- **Java `AuthConfigs`**:与 Schema 同步(表见轨道 C BP-10)。
- **`examples/`**:与最新字段及环境变量说明一致;CI 与 Schema 校验对齐。
- **文档**:明确 **SDK 版本线 ≠ 平台 Fat JAR 版本**;引用 BPM **BP-10** 与产品 M6-F01/F02 口径。
- **不做**Native 与 Webhook 运行时耦合;平台不嵌入 JNI。
---
## Part B — I6:规划与收口文档
| 主题 | 内容要点 | 执行文档 |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| **UAT 门禁** | 跑通 **BP-0106、11** 主链路(与 [轨道 B I6](../tracks/02-frontend-platform-ui.md) E2E 一致);**Callback** 场景含重复投递幂等、关联失败人工挂接。 | [I6_CLOSEOUT.md §2](./I6_CLOSEOUT.md) |
| **冻结清单** | **SDK**:定版 tag、CHANGELOG、**BitAnswer 兼容矩阵**(轨道 C);**OpenAPI** 快照冻结;**两 JAR** 版本与镜像标签可追踪;前端 **`VITE_API_BASE`** 环境矩阵文档化。 | [I6_CLOSEOUT.md §3~§4](./I6_CLOSEOUT.md) |
| **Runbook** | [`services/RUNBOOK.md`](../../../services/RUNBOOK.md)**内部 token 轮换**、Webhook→平台连通性检查、DB 迁移顺序(`flyway_platform_api` / `flyway_webhook`)。 | RUNBOOK **§10** |
| **安全加固** | **安全响应头**、Cookie/Session 策略(若 Mid 前仍为 JWT 则文档化 **仅 Bearer**)、依赖扫描与已知 CVE 处理;**禁止** I6 周排入大块新功能(仅缺陷与加固)。 | 平台 `SecurityConfig` + Runbook + [CI 依赖/CVE 扫描](../../../.github/workflows/ci-security.yml) |
| **实现审核** | I1~I6 对照设计与三轨道文档 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) |
---
## Part C — 实现顺序与 MVP 切割
1. **平台 DBFlyway `V5+`**`platform_callback_inbox` + M6 两表 + 必要索引与外键;种子数据(环境/产品线)可选。
2. **平台内部 API**`POST /internal/v1/callback-events` + 幂等与 `schemaVersion` 校验 + **`AuditService` 钩子**(状态/关联变更)。
3. **平台公开 API**`GET/PATCH` callback-inbox、M6 只读 GET**统一异常**经 `ApiExceptionHandler`
4. **OpenAPI 快照** 与契约测试更新。
5. **Webhook**:收据落库后 **入队平台投递****I7**`webhook_platform_delivery` + 调度);配置项:`PLATFORM_INTERNAL_BASE_URL``PLATFORM_INTERNAL_TOKEN`
6. **集成测试**Testcontainers + 双模块或 Compose(与轨道 A **I5+ `cross-service-it`** 建议一致)。
7. **前端**:路由与表格/详情/脱敏展示;联调 staging。
8. **SDK / Schema / examples**:按 BP-10 终版对齐并过 CI。
**MVP 明确推迟(若全量 M5/M6 过大)**
| 推迟项 | 说明 |
| ---------- | ------------------------------------------ |
| M6-F03F09 | 比特 ID 映射、特征映射、模板库、发布记录、影响分析 |
| M5-F06~F09 | 失败分类字典、批量重试 UI、积压监控、M8 待办联动 |
| M5-F10 | 模拟投递 UI(可用 curl/Postman 代替) |
| MQ 投递 | 保留 HTTP MVPMQ + 消费者为 ADR 备选(轨道 A 已列 B 方案) |
---
## Part D — 可追溯性(设计章节 → 产品功能点)
| 设计章节 | 产品模块 / 功能点(适用处) |
| ------------------------------- | ------------------------------ |
| A.1 幂等与 schemaVersion | M5 运营基础;BP-06 |
| A.2.1 `platform_callback_inbox` | **M5-F01F04**(列表、详情、状态、关联兜底) |
| A.2.1 `event_type`、字典 | **M5-F05** |
| A.2.2 产品线 / 环境表 | **M6-F01、M6-F02** |
| A.3 公开 REST | **M5-F01F03**;人工挂接 **M5-F04** |
| A.4 内部 API | BP-06 入站;与轨道 A Webhook↔平台契约 |
| A.5 Webhook 转发 | BP-06 步骤①②;**不丢链** |
| A.8 SDK / Schema | **BP-10**;**M6** 配置治理(文档与校验链) |
| Part B I6 | BP-0106、11 UATM11 安全与运维 |
---
## 修订记录
| 日期 | 说明 |
| ---------- | ----------------------------------------------------------------- |
| 2026-04-06 | 初版:I5/I6 架构设计,对齐并行索引、三轨道文档与产品 M5/M6 P0。 |
| 2026-04-06 | Part B 关联 I6 收口文档(CLOSEOUT / IMPLEMENTATION_REVIEW)与 RUNBOOK §10。 |
| 2026-04-06 | 固化 Markdown 引用与强调;Part B 补充 CI 依赖/CVE 扫描入口。 |
| 2026-04-06 | 对齐 **I7**A.3/A.5/A.7 权限与异步出库;OpenAPI/链接格式修正。 |
@@ -0,0 +1,69 @@
# I6 收口执行包(UAT 门禁、冻结清单、运维)
> **定位**:在 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md) **Part B** 规划基础上,把 **I6 周可执行项** 收束为检查表与环境矩阵。
> **前置**I5 代码路径已合入 `develop`Callback Inbox、内部投递、Webhook 转发、集成只读 API、前端路由)。
> **实现对照审核**[I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md)。
---
## 1. 架构师任务(I6 周入口)
| 次序 | 产出 | 责任 |
|------|------|------|
| 1 | 本文件 + Runbook §10 运维段落 | 架构 / Tech Lead |
| 2 | 后端:安全响应头、配置与观测无回归 | 后端 |
| 3 | 前端:`VITE_API_BASE`、首页 UAT 导航、构建说明 | 前端 |
| 4 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 关闭 I1–I6 偏差项 | 架构审核 |
---
## 2. UAT 门禁(P0 场景)
> 与 [轨道 B §2 I6](../tracks/02-frontend-platform-ui.md)「BP-0106+11」一致;以下按 **本工作区已实现能力** 细化。
| # | 场景 | 预期 | 备注 |
|---|------|------|------|
| U1 | 登录 JWT | `admin/admin` 登录后访问受保护路由 | I1 |
| U2 | 客户 → 项目 | CRUD 与列表 | I2 |
| U3 | 合同 | 创建、行项、状态迁移合法/非法 | I3 |
| U4 | 交付批次 → 许可 SN | 创建、详情、状态 | I4 |
| U5 | Callback 全链 | Webhook 收据后转发 → 平台 Inbox 一行;UI 列表/详情、状态 PATCH、可选 link | I5;需配置内部 Token 与 base-url |
| U6 | 集成只读 | 环境/产品线列表与详情 | I5 |
| U7 | 重复幂等 | 同 `Idempotency-Key` / 同 `externalMessageId` 不重复插入;内部 API 返回 `duplicate: true` | I5 |
| U8 | 401 统一 | Token 失效回登录带 redirect | I1 |
**UAT 退出条件**:上表 **无 P0 缺陷**;已知 P1/P2 记入工单或 [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 「已知局限」。
---
## 3. 冻结清单(I6 末)
| 项 | 动作 |
|----|------|
| **OpenAPI** | `contracts/openapi/delivery-platform-api.json``OpenApiContractSnapshotTest` 一致;打 tag 可追溯 |
| **两枚 Fat JAR** | `delivery-platform-api``license-webhook-ingress` 版本与镜像标签写入发布说明 |
| **前端** | 生产构建使用明确 `VITE_API_BASE`(见 §4 |
| **SDK(本仓)** | 定版 tag、`CHANGELOG`、[轨道 C](../tracks/03-client-sdk.md) **兼容矩阵** 填齐;**I6 周内禁止 MAJOR Schema**(与设计一致) |
| **内部 Token** | 平台 `PLATFORM_INTERNAL_TOKEN` / `CRAFTLABS_PLATFORM_INTERNAL_TOKEN` 与 Webhook `craftlabs.platform.internal.token` **同值**;轮换走 [RUNBOOK §10](../../../services/RUNBOOK.md) |
| **依赖 / CVE** | PR 合并前 **`ci-security`** 通过:Trivy 扫描 `services/``java/``CRITICAL`+`HIGH`,仅已修复项见 workflow);`npm audit --audit-level=high``web/delivery-platform-ui`)。**Dependabot** 周度提 PR`.github/dependabot.yml`)。 |
---
## 4. 前端 `VITE_API_BASE` 环境矩阵
| 环境 | 示例 `VITE_API_BASE` | 说明 |
|------|----------------------|------|
| 本地开发 | (不设) | Vite 代理 `/api``127.0.0.1:8080` |
| Staging | `https://platform-api.staging.example.com` | 无尾部斜杠;axios 请求 `/api/v1/...` |
| 生产 | `https://platform-api.example.com` | 同源反代时可设为空,由 Nginx 处理 `/api` |
构建:`VITE_API_BASE=https://… npm run build`。详见 `web/delivery-platform-ui/README.md`
---
## 5. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | I6 收口执行包初版:UAT 表、冻结清单、VITE 矩阵。 |
| 2026-04-06 | 冻结清单补充 CI 依赖/CVE 与 Dependabot。 |
@@ -0,0 +1,91 @@
# 架构师审核:I1~I6 实现对照(本工作区 `develop`)
> **方法**:以 [并行索引](../PARALLEL_ITERATION_INDEX.md)、各迭代设计稿(如 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md))、三轨道文档([01](../tracks/01-backend-platform-webhook.md) / [02](../tracks/02-frontend-platform-ui.md) / [03](../tracks/03-client-sdk.md))为预期,对照仓库 **当前实现** 做收口审计。
> **范围**`services/delivery-platform-api`、`services/license-webhook-ingress`、`web/delivery-platform-ui`、`contracts/openapi`、`schemas/` 及 CI/Enforcer 门禁;**不**评价未在本仓的独立 Fat JAR 发布物。
---
## 1. 总评
| 维度 | 结论 |
|------|------|
| **迭代完整性** | I1I5 主路径已在前后端与 Webhook 贯通;I6 以 **UAT 文档、Runbook、安全头、前端生产基址与导航** 收口,符合 Part B「无大块新功能」原则。 |
| **契约** | 公开 API 以 `contracts/openapi/delivery-platform-api.json` 为 SSOT`OpenApiContractSnapshotTest` 守门;`/internal/**` 排除默认 springdoc 分组,与设计一致。 |
| **风险** | 内部 Token 为 MVP 共享密钥;生产应规划 mTLS/签名(设计已提示)。Webhook 转发失败仅日志重试次数用尽,**无持久化 DLQ**,适合 I6 后需求排期。 |
---
## 2. 分项审核
### 2.1 I1 — 身份、JWT、壳层
| 预期 | 实现 | 偏差 |
|------|------|------|
| Bearer JWT、401 入口、路由 RBAC | `JwtAuthenticationFilter``SecurityConfig` JWT 链、`router` meta.roles | 无 |
| 登录与 ping | `AuthController``/api/v1/ping`、前端 `LoginView` | 无 |
### 2.2 I2 — M1 主数据
| 预期 | 实现 | 偏差 |
|------|------|------|
| 客户/项目 CRUD + 字典 | Controllers + `CustomersView` / `ProjectsView` | 无结构性偏差(字段级以 OpenAPI 为准) |
### 2.3 I3 — M2 合同 + M10-F01
| 预期 | 实现 | 偏差 |
|------|------|------|
| 合同与行项、状态机服务端校验 | Contract API + 前端向导/详情 | 历史曾出现前后端动词不一致,**当前以 PATCH status 等与后端对齐**(需在 UAT 再点检) |
| 审计 | `AuditService``AuditEntityTypes` / `AuditActions` | 已扩展 Callback 相关常量(I5 |
### 2.4 I4 — M3/M4 交付与 SN
| 预期 | 实现 | 偏差 |
|------|------|------|
| 交付批次、行、许可 SN | 对应 API + `DeliveriesView` / SN 向导与列表 | 与设计 P0 一致 |
### 2.5 I5 — M5 Inbox、M6 只读、Webhook 链
| 预期(见 I5_I6_DESIGN Part A | 实现 | 偏差 / 备注 |
|--------------------------------|------|----------------|
| `platform_callback_inbox` + M6 表 + Flyway V5 | 已落地 | 无 |
| 公开 `GET/PATCH` callback-inbox、integration GET | 已落地 | 无 |
| `POST /internal/v1/callback-events` + 内部 Token | `CallbackInternalController``InternalTokenAuthenticationFilter` | 无 |
| 幂等 `(sourceSystem, externalMessageId)` + 重复返回 `duplicate` | `CallbackEventIngestService` | 无 |
| `schemaVersion` major 校验 | `SUPPORTED_SCHEMA_MAJOR = 1` | 与设计「拒绝无法识别的 major」一致 |
| Webhook:收据后转发、trace 头、有限重试 | `PlatformCallbackForwarder` | **MVP**:同步线程内 3 次退避;与设计「异步重试」相比属简化,Runbook 已说明可配置性 |
| 前端 `/callbacks`、脱敏 | `redactPayload.js`、Inbox 视图 | 建议 UAT 确认敏感字段字典与产品一致 |
| OpenAPI 仅公开路由 | 内部 `@Hidden` + `paths-to-exclude` | 无 |
### 2.6 I6 — UAT、冻结、加固
| 预期(Part B + I6_CLOSEOUT | 实现 | 偏差 |
|-------------------------------|------|------|
| UAT 检查表 | [I6_CLOSEOUT.md](./I6_CLOSEOUT.md) | 过程性文档;**不替代**自动化 E2E |
| Runbook:内部 Token、连通性、轮换 | [RUNBOOK.md §10](../../../services/RUNBOOK.md) | 无 |
| 安全响应头与依赖门禁 | `SecurityConfig.apiHeaders``X-Content-Type-Options`、frame deny、Referrer-Policy**`ci-security`**Trivy + `npm audit`)与 **Dependabot** | **HSTS** 依设计交由边缘层 |
| 前端生产 `VITE_API_BASE` | `main.js` + README + CLOSEOUT §4 | 无 |
| 首页全链路导航 | `HomeView` 模块链接 | 满足轨道 B I6「全链路导航」最低要求 |
### 2.7 轨道 CSDK / Schema
| 预期 | 实现 | 偏差 |
|------|------|------|
| I5 硬交付 Schema/示例/AuthConfigs | 以本仓 `schemas/``java/`、CI 为准(参见轨道 C 文档) | **I6 冻结**需在发布周由发布 Owner 执行 tag/CHANGELOG/矩阵,**非本审计单次提交所能证明** |
---
## 3. 建议进入 I7 / V1.1 前跟踪项
1. **Webhook 投递**:评估异步队列或 Outbox,补 **DLQ 指标**(设计 A.5、M5-F08)。
2. **内部认证**:mTLS 或请求签名;Token 多版本滚动。
3. **Playwright/Cypress**:将 I6_CLOSEOUT §2 固化为流水线冒烟。
4. **权限细化**M5 运营接口由 SYS_ADMIN/DEVELOPER 收口为 Ops 角色(设计 A.3 已注明 I7+)。
---
## 4. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I6 架构师审核,对照 I5_I6_DESIGN 与三轨道文档。 |
| 2026-04-06 | I6 表:补充 CI 依赖/CVE 与 Dependabot。 |
+122
View File
@@ -0,0 +1,122 @@
# 迭代 I7 架构设计 — 可靠投递、运营权限、前端指令
> **仓库**`craftlabs-authorization-sdk``develop`)。
> **前置**[I6 收口](./I6_CLOSEOUT.md)、[I6 实现审核](./I6_IMPLEMENTATION_REVIEW.md) §3 跟踪项。
> **角色**:迭代功能架构说明;实现以本文件为审查基线。
---
## 1. 目标与范围
| 主题 | I7 要达成 | 非目标(后置) |
| ---------------- | -------------------------------------------------------------------------------------------------- | ----------------------- |
| **Webhook → 平台** | 对比特 **2xx 后立即返回**;平台投递 **异步**、可重试、**落库可观测**(状态/次数/最后错误) | MQ、跨地域多活 |
| **运营权限** | 新增 `**OPS`****Callback Inbox**(读/改/挂接)仅 `**OPS` + `SYS_ADMIN`**`**DEVELOPER**` 保留其余业务与 **M6 只读** | 细粒度按钮与数据范围(I8+) |
| **前端** | 路由 `meta.roles` 与菜单 `**hasAnyRole`** 一致;登录页说明 `**ops/ops**` | Playwright 流水线(I7.1 可选) |
---
## 2. Webhook 侧:平台投递出站队列
### 2.1 行为
1. `**webhook_callback_receipt` 首次插入成功** 且配置了 `craftlabs.platform.internal.base-url` + token 时,写入 `**webhook_platform_delivery`**`status=PENDING`
2. **不**在 Callback HTTP 线程内同步 `RestClient` 调用平台(避免比特侧拖慢与线程占用)。
3. `**@Scheduled`** 周期拉取 `PENDING` / 可重试失败行,`POST /internal/v1/callback-events`;成功 → `SENT`;失败 → `attempts++`,指数退避 `**next_retry_at**`,超过 `**max-attempts**``DEAD`(人工/运维处理)。
4. **未配置 base-url** 时:不建队列行(与 I5「仅收据」行为一致)。
### 2.2 表(Flyway `V2__webhook_platform_delivery.sql`
| 列 | 说明 |
| ----------------------------------------- | ------------------------------------- |
| `receipt_id` | 关联收据 |
| `request_body` | 平台 API JSON 正文 |
| `trace_headers_json` | `traceparent` / `X-Request-Id` 等 JSON |
| `idempotency_key` | 头 `Idempotency-Key` 用值 |
| `status` | `PENDING` / `SENT` / `DEAD` |
| `attempts`, `last_error`, `next_retry_at` | 重试与排障 |
### 2.3 配置(示例)
| Key | 说明 |
| ----------------------------------------------- | ----------- |
| `craftlabs.platform.delivery.scheduler-enabled` | 单测可 `false` |
| `craftlabs.platform.delivery.max-attempts` | 默认 `8` |
| `craftlabs.platform.delivery.batch-size` | 每.tick 处理条数 |
---
## 3. 平台 API:角色与方法安全
### 3.1 角色
| 角色 | 用途 |
| ----------- | ----------------------------------------------------------- |
| `SYS_ADMIN` | 全量(含 Callback |
| `OPS` | **仅** Callback Inbox + 与运营配套能力;**不**开放合同/交付等业务写路径(由路由与菜单裁剪) |
| `DEVELOPER` | 业务+M6 只读;**不**含 Inbox |
### 3.2 安全模型
- `**@EnableMethodSecurity`** + `**@PreAuthorize**` 扛后端契约。
- `**CallbackInboxController**``hasAnyRole('OPS','SYS_ADMIN')`
- `**IntegrationCatalogController**``hasAnyRole('OPS','SYS_ADMIN','DEVELOPER')`
- JWT 仍由 `[JwtAuthenticationFilter](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/security/JwtAuthenticationFilter.java)` 注入 `ROLE_*`
### 3.3 演示账号
| 用户 | 密码 | 角色 |
| ------- | ------- | ----------- |
| `admin` | `admin` | `SYS_ADMIN` |
| `dev` | `dev` | `DEVELOPER` |
| `ops` | `ops` | `OPS` |
---
## 4. 前端(`delivery-platform-ui`
### 4.1 路由 `meta.roles`
| 区域 | `meta.roles` |
| ------------------------------ | ------------------------------- |
| 首页及以下业务(客户/项目/合同/交付/SN) | `SYS_ADMIN`, `DEVELOPER` |
| `/callbacks`, `/callbacks/:id` | `SYS_ADMIN`, `OPS` |
| `/integration/*` | `SYS_ADMIN`, `DEVELOPER`, `OPS` |
### 4.2 菜单与首页
- **Pinia** `hasAnyRole(roles)`**MainLayout** / **HomeView** 按角色显示入口,避免 OPS 误以为可点业务页。
---
## 5. 测试与 DoD
| 项 | 标准 |
| ------- | ----------------------------------------------------------------------------------- |
| 平台 | `CallbackInboxControllerTest``DEVELOPER` 访问 Inbox → **403**`SYS_ADMIN`/`OPS` 仍可走通 |
| Webhook | 配置 base-url 时 **enqueue** 产生 `PENDING` 行;单测关闭 scheduler 避免后台线程 |
| 前端 | `npm run build` 通过 |
---
## 6. 修订记录
| 日期 | 说明 |
| ---------- | ---------------------------- |
| 2026-04-06 | I7 初版:异步投递表 + OPS + 前端路由/菜单。 |
| 2026-04-06 | 闭环:实现与 [I7_IMPLEMENTATION_REVIEW.md](./I7_IMPLEMENTATION_REVIEW.md) 复盘。 |
@@ -0,0 +1,44 @@
# I7 实现复盘 — 对照 [I7_DESIGN.md](./I7_DESIGN.md)
> **方法**:三任务闭环——架构设计 → 前后端实现 → 本复盘。
> **日期**2026-04-06。
---
## 1. 总评
| 主题 | 设计意图 | 实现结论 |
|------|----------|----------|
| Webhook 异步投递 | 对比特快速 2xx;平台 POST 后台重试;可观测 `PENDING/SENT/DEAD` | **已落地**`webhook_platform_delivery` + `PlatformDeliveryService` + `PlatformDeliveryScheduler`;配置见 `craftlabs.platform.delivery.*` 与 [RUNBOOK §10.5](../../services/RUNBOOK.md)。 |
| OPS 与 Inbox | `CallbackInboxController``OPS`/`SYS_ADMIN` | **已落地**`@PreAuthorize` + 演示账号 `ops/ops`。 |
| M6 只读 | `IntegrationCatalogController` 三者可读 | **已落地**`OPS`+`SYS_ADMIN`+`DEVELOPER`。 |
| 前端 | 路由 `meta.roles` 与侧栏一致 | **已落地**`HomeView` 过滤链接;`MainLayout` `v-if`;首页含 `OPS`。 |
---
## 2. 偏差与已知局限
| 项 | 说明 |
|----|------|
| **DEAD 行运维** | **I8** 已提供平台代理重放 + 详情页按钮;**I9** 补充出库 **只读状态**`PENDING`/`SENT`/`DEAD` 等)见 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)。 |
| **`v-permission` 指令** | 设计可选组件级指令;当前以 **路由 + 菜单 `hasAnyRole`** 为主,足够覆盖 I7 DoD。 |
| **Playwright** | 仍未进 CI;与 [I6_CLOSEOUT](./I6_CLOSEOUT.md) 一致列为后续。 |
| **内部 mTLS** | 未在本次范围;仍共享 `X-Platform-Internal-Token`。 |
---
## 3. 验证清单(走查)
- [x] `mvn -f services/pom.xml verify`
- [x] `web/delivery-platform-ui` `npm run build`
- [x] `AuthControllerTest` ops 登录
- [x] `CallbackInboxControllerTest``dev` → Inbox **403**
- [x] `PlatformDeliveryEnqueueTest`:首单 Callback → 队列 **+1**
---
## 4. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I7 闭环复盘。 |
@@ -0,0 +1,40 @@
# I8 实现审核 — 对照 [I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)
> **日期**2026-04-06。
---
## 1. 总评
| 设计条款 | 结论 |
|----------|------|
| 浏览器只调平台、平台代调 Webhook | **已落地**`POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` + `WebhookDeliveryReplayClient`。 |
| Webhook `/internal/**` 独立 Ops Token | **已落地**`WebhookOpsTokenFilter``X-Webhook-Ops-Token`;空配置 **503**。 |
| 仅 `DEAD` 可重放 | **已落地**`PlatformDeliveryService.replayDeadDeliveryByReceiptId`。 |
| 关联 `webhookReceiptId` | **已落地**:平台从 `PlatformCallbackInbox` 解析 `Long` 调 Webhook。 |
| OpenAPI / Runbook | **已更新**`CallbackWebhookReplayResponse`、RUNBOOK §10.5 I8 段。 |
---
## 2. 偏差与后续
| 项 | 说明 |
|----|------|
| **UI 不展示出库状态** | 仍为「成功调用平台接口」提示;是否在详情旁拉取 Webhook 库属 **Mid**(需只读 API 或观测面)。 |
| **Playwright** | 未进 CI;与 I6/I7 一致可后续补一条 OPS 点击重放(需 mock Webhook 或 test 栈)。 |
---
## 3. 验证命令(提交前)
- `mvn -f services/pom.xml verify`
- `npm run build``web/delivery-platform-ui`
- `UPDATE_OPENAPI=0 mvn -f services/pom.xml test -Dtest=OpenApiContractSnapshotTest`(契约已与 `contracts/openapi/delivery-platform-api.json` 手工对齐时亦应通过)
---
## 4. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I8 实现对照设计审核。 |
@@ -0,0 +1,88 @@
# I8 设计 — Webhook 平台投递 DEAD 重放(MVP
> **角色**:解决方案架构;指导前后端实现与 Runbook。
> **前置**I7 已落地 `webhook_platform_delivery``PENDING`/`SENT`/`DEAD`)与调度器;平台 `platform_callback_inbox.webhook_receipt_id` 与 Webhook 收据主键对齐(`PlatformCallbackRequestPlanner` 写入 `webhookReceiptId`)。
---
## 1. 问题与目标
| 维度 | 说明 |
|------|------|
| **问题** | 出库 `DEAD` 后仅能通过查库/SQL 手工改状态,无受控运维入口,易误操作。 |
| **目标** | Ops 在 **Callback 详情** 一键将对应 **`DEAD`** 投递重新入队(`PENDING`),由现有调度器按 `max-attempts` 再次尝试 `POST /internal/v1/callback-events`。 |
| **非目标** | 修改比特 Callback 契约;不实现全量 DLQ 控制台;不在浏览器直连 Webhook 服务。 |
---
## 2. 信任边界与 API 分层
```mermaid
flowchart LR
UI[delivery-platform-ui OPS JWT]
API[delivery-platform-api]
WH[license-webhook-ingress]
UI -->|Bearer JWT| API
API -->|X-Webhook-Ops-Token + receiptId| WH
WH -->|调度| API
```
1. **浏览器只调平台** `POST /api/v1/callback-inbox/{id}/replay-webhook-delivery`,沿用 `CallbackInboxController``@PreAuthorize("hasAnyRole('OPS','SYS_ADMIN')")`
2. **平台服务器到 Webhook** 使用 **独立共享密钥** `X-Webhook-Ops-Token`(与 `X-Platform-Internal-Token` 分离:前者保护 Webhook 运维面,后者保护平台内部 ingest)。
3. **Webhook** 暴露 `POST /internal/v1/platform-deliveries/by-receipt/{receiptId}/replay`,由 **Servlet Filter** 校验 Ops Token(模块无 Spring Security 依赖,与现有 `/webhook/bitanswer/callback` 并存)。
---
## 3. 业务规则
| 规则 | 说明 |
|------|------|
| **关联键** | 使用平台收件箱的 `webhook_receipt_id`(字符串数字)对应 `webhook_platform_delivery.receipt_id`(唯一)。缺失则 **400**,提示未关联 Webhook 出库记录。 |
| **仅 DEAD 可重放** | Webhook 侧若行不存在 → **404**;若状态为 `PENDING`/`SENT`**409**,避免误重置进行中或已成功任务。 |
| **重放语义** | `status=PENDING``attempts=0``last_error=NULL``next_retry_at=NULL``updated_at=now`(重新给满 `max-attempts` 次数)。 |
| **平台幂等** | 重放仅重新 HTTP 投递;若平台 Inbox 已存在同 `(sourceSystem, externalMessageId)`,内部 ingest 返回 duplicate**不新建 Inbox**(与现有幂等一致)。 |
---
## 4. 配置项
| 组件 | 属性 / 环境变量 | 说明 |
|------|-----------------|------|
| Webhook | `craftlabs.webhook.ops-token` / `LICENSE_WEBHOOK_OPS_TOKEN` | 非空才启用 `/internal/**` 鉴权;空则 **503** 所有内部运维路径(避免误暴露)。 |
| Platform | `craftlabs.webhook.base-url` / `LICENSE_WEBHOOK_BASE_URL` | Webhook 根协议+主机+端口。 |
| Platform | `craftlabs.webhook.ops-token` | 与 Webhook 相同密钥,仅驻内存。 |
**Runbook**:在 [RUNBOOK.md](../../../services/RUNBOOK.md) §10.5 旁补充:重放前确认平台已恢复;同 receipt **不要并发多次重放**(单机调度即可)。
---
## 5. 前端
- **Callback 详情**:展示 `webhookReceiptId`;若存在则显示 **「重新入队出库(DEAD→待投递)」**,调平台 POST;成功/失败用现有 `apiErrorMessage`
- **可见性**:路由已为 OPS/SYS_ADMIN,与 I7 一致。
---
## 6. 契约
- 更新 `contracts/openapi/delivery-platform-api.json``POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` 与响应 DTO(如 `status``receiptId`)。
- Webhook 内部路由 **不入** 对外 OpenAPI。
---
## 7. 测试与验收
| 层级 | 验收 |
|------|------|
| Webhook | 单测:`replayDeadByReceiptId` 对 DEAD/非 DEAD/缺行行为;集成或 MockMvc401/503 无 token。 |
| Platform | 单测或 Mock`CallbackInboxService` 在缺 receipt、Webhook 409/404 时映射 HTTP。 |
| 手工 | DEAD 一行 → 详情点重放 → 行变 `PENDING` → 调度成功后 `SENT`。 |
---
## 8. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I8 DEAD 重放架构(平台代理 + Webhook 内部 API)。 |
@@ -0,0 +1,44 @@
# I9 实现审核 — 对照 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)
> **日期**2026-04-07。
---
## 1. 总评
| 设计条款 | 结论 |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Webhook 只读 `GET by-receipt` | **已落地**`PlatformDeliveryService#getStatusByReceiptId` + `WebhookPlatformDeliveryOpsController`。 |
| 平台代理 + JWT | **已落地**`GET /api/v1/callback-inbox/{id}/webhook-delivery``WebhookDeliveryReplayClient#fetchDeliveryStatus`。 |
| 前端不直连 Webhook | **已落地**`getCallbackWebhookDelivery` + 详情 `el-descriptions`;失败仅提示文案不遮断主详情。 |
| OpenAPI | **已导出**`UPDATE_OPENAPI=1` 更新 `contracts/openapi/delivery-platform-api.json`。 |
| 文档索引 / I7 复盘 | **已修订**[PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md)、[I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2。 |
---
## 2. 后续(未在本迭代范围)
| 项 | 说明 |
| -------------- | ----------------------------------------------- |
| **Playwright** | 可对「有 webhookReceiptId 的详情」断言状态区块或 503 提示。 |
| **指标** | M5-F08:积压 `PENDING`/`DEAD` 仍以观测平台为准,本迭代仅 UI 只读。 |
---
## 3. 验证
- `JAVA_HOME`=JDK **17**`mvn -f services/pom.xml verify`
- `npm run build``web/delivery-platform-ui`
---
## 4. 修订记录
| 日期 | 说明 |
| ---------- | --- |
| 2026-04-07 | 初版。 |
@@ -0,0 +1,68 @@
# I9 设计 — Callback 详情可观测 Webhook 平台投递状态
> **角色**:架构审查与实现规划;承接 [I8](I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)(重放入队)之后的 **只读观测** 缺口。
> **日期**2026-04-07。
---
## 1. 走查结论(相对 I6/I7/I8
| 主题 | 状态 | 说明 |
|------|------|------|
| I7 异步出库 | 已落地 | `PENDING` / `SENT` / `DEAD` 在 Webhook 库表。 |
| I8 重放 | 已落地 | 平台 POST 代理 + Webhook `replay`;**详情页仍看不到实时出库状态**。 |
| I7 复盘 §2 DEAD | **文档过时** | 已具备 UI 重放(I8);本迭代补 **状态只读** 并修订 I7 复盘表述。 |
| OpenAPI / JDK | 门禁 | 变更契约后须 **JDK 17** + `OpenApiContractSnapshotTest`;见 [contracts/README](../../../contracts/README.md)。 |
---
## 2. 目标与非目标
| **目标** | Ops 在 **Callback 详情** 查看与该收件箱 `webhookReceiptId` 对应的 **`webhook_platform_delivery` 行摘要**`status``attempts``lastError``nextRetryAt``updatedAt`),无需直连 Webhook 库。 |
| **非目标** | 浏览器调用 Webhook;改动比特契约;全量 M5-F08 仪表盘;Playwright 进 CI(列为 I9 后可选)。 |
---
## 3. API 与信任边界
与 I8 相同:**仅** `delivery-platform-api`**`LICENSE_WEBHOOK_BASE_URL` + `LICENSE_WEBHOOK_OPS_TOKEN`** 访问 Webhook **`/internal/**`**;前端只打平台 JWT。
| 组件 | 方法 | 路径 | 说明 |
|------|------|------|------|
| Webhook | `GET` | `/internal/v1/platform-deliveries/by-receipt/{receiptId}` | 同 `WebhookOpsTokenFilter`;无行则 **404**。 |
| Platform | `GET` | `/api/v1/callback-inbox/{id}/webhook-delivery` | `@PreAuthorize` 与 Inbox 一致(`OPS`/`SYS_ADMIN`);无 `webhookReceiptId`**400**;未配 Webhook 则 **503**。 |
**响应体(双方 JSON 字段一致,便于直连 RestClient 反序列化)**
- `receiptId` (long)
- `status` (string)
- `attempts` (int)
- `lastError` (string, 可 null)
- `nextRetryAt` (date-time, 可 null)
- `updatedAt` (date-time)
---
## 4. 实现要点
1. **Webhook**`PlatformDeliveryService#getStatusByReceiptId` 查表组装 Map/DTOController 增加 `GET`
2. **平台**`WebhookDeliveryReplayClient#fetchDeliveryStatus`(保留 Bean 名以减重构面);`CallbackInboxService#getWebhookDeliveryStatus``CallbackInboxController`
3. **前端**:详情页在存在 `webhookReceiptId` 时请求上述 GET,展示 `el-descriptions`;失败时 **不阻断** 主详情(提示或短文案)。
4. **契约**`UPDATE_OPENAPI=1` 导出快照并提交。
5. **文档**:更新 [PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md) I9 行;修正 [I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2 DEAD 表述。
---
## 5. 验证
- `mvn -f services/pom.xml verify`Java 17
- `npm run build``web/delivery-platform-ui`
- Webhook / 平台单测各 ≥1 条覆盖 GET 与平台代理
---
## 6. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-07 | 初版:I9 出库可见性架构与 API。 |
@@ -0,0 +1,132 @@
# 轨道 A:平台 API + License Webhook 后端 — 并行实施包
> **对齐**[BPM 排期 §7](../chuangfei-platform-bpm-and-roadmap.md) · [工程布局 §5](../WORKSPACE_ENGINEERING_LAYOUT.md) · [集成平台 §8](../../chuangfei-bitanswer-integration-platform.md)
> **约束**Spring Boot **4.0.x**Maven **多模块****仅 `*-bootstrap`** `repackage` → **各一枚 Fat JAR**;服务端 **禁止** `craftlabs-auth-bitanswer`;数据层 **PostgreSQL 15** + **MyBatis-Plus**(见 [WORKSPACE_ENGINEERING_LAYOUT.md §9.1](../WORKSPACE_ENGINEERING_LAYOUT.md))。
---
## 0. 并行执行总览
| 维度 | 说明 |
|------|------|
| **两条可部署线** | `delivery-platform-api``license-webhook` **同一日历迭代并行**,各产 **一枚 Fat JAR**。 |
| **对齐机制** | **契约 Owner**(事件 schema、`Idempotency-Key`、错误码);共享 **OpenAPI / 事件 README**;**I5 起** 集成测试门禁。 |
| **I1I4 Webhook** | 可先 **验签、幂等、观测、占位路由**;生产级持久化 + M5 台账对齐在 **I5**。 |
---
## 1. 仓库与模块骨架
### 1.1 双仓库(推荐)
| 仓库(示例名) | 产出 |
|----------------|------|
| `craftlabs-delivery-platform` | `platform-bootstrap-*.jar` |
| `craftlabs-license-webhook` | `webhook-bootstrap-*.jar` |
### 1.2 `delivery-platform` 模块示例
```
delivery-platform/
├── pom.xml
├── platform-domain/
├── platform-application/
├── platform-adapters-web/
├── platform-adapters-persistence/
└── platform-bootstrap/ # 唯一 main + repackage
```
包名建议:`cn.craftlabs.platform.{identity,customer,contract,delivery,licensing,audit,config,...}`
### 1.3 `license-webhook` 模块示例
```
license-webhook/
├── pom.xml
├── webhook-domain/
├── webhook-application/
├── webhook-adapters-http/
├── webhook-adapters-integration/
└── webhook-bootstrap/
```
包名建议:`cn.craftlabs.webhook.*`
---
## 2. 分迭代后端 BacklogI1I6
| 迭代 | 平台:模块 | 平台:REST/领域 | 平台:DB | Webhook:范围 | 集成点 | DoD |
|------|------------|-----------------|----------|---------------|--------|-----|
| **I1** | domain/application/adapters-web/persistence/bootstrap | M11 登录/登出/会话、登录审计、粗 RBAC、OpenAPI 初版 | 用户、角色、会话、登录审计 | 脚手架、CI、`/health`、Callback 占位、`Idempotency-Key` 记录 | 无强依赖 | 平台 Fat JAR`dependency:tree` 无 bitanswerWebhook Fat JAR CI 绿 |
| **I2** | +M1 | 客户/项目 CRUD、字典、用户角色分配 API | 客户、项目、字典 | 验签配置、拒绝非法请求指标 | 字典编码与前端约定 | M1 API 与鉴权打通;验签可 curl 演示 |
| **I3** | 加重 persistence | M2 合同+行项、状态机;M10-F01 | 合同、行、变更日志 | 事件 DTO 规范化、与平台枚举对齐;可 Mock DB | 契约:合同/行 id 供 Callback 关联 | 状态机单测;事件 schema v0.1 归档 |
| **I4** | | M3 交付;M4 SN 录入/绑定/状态/手工回写 | 交付、SN、FK | 关联键策略、MQ producer 骨架可选 | 内部 API 或 MQ 方案 ADR | 交付+SN 主键链路可追溯 |
| **I5** | | M5 Inbox P0M6 环境+产品线最小 | Inbox 唯一约束、M6 表 | 验签→幂等落库/入队→平台可见 | 比特 Dev 联调 | E2E:模拟 Callback → 平台 DB 一条 Inbox |
| **I6** | bootstrap 横切 | 加固、Runbook、UAT 缺陷 | 修复类迁移 | Ingress 加固、密钥轮换演练 | 全链路 BP-01~06、11 | UAT 签字;两 JAR 版本可追踪 |
### 并行分工小结
| 迭代 | 平台关键路径 | Webhook 关键路径 |
|------|--------------|------------------|
| I1I2 | 身份 + 主数据 | Ingress + 验签 + 契约草稿 |
| I3I4 | 合同/交付/SN + 审计 | 事件模型 + 投递骨架 |
| I5I6 | Inbox + M6 | 真实链路 + 运维 |
---
## 3. Webhook ↔ 平台契约要点
- **版本**`schemaVersion``X-Event-Schema-Version`
- **幂等**`Idempotency-Key` + 比特稳定 `message_id`Inbox 表 `(source_system, external_message_id)` 唯一。
- **投递**A) Webhook **HTTP** `POST /internal/v1/callback-events`B) **MQ** + 平台消费者(MVP 需 I5 定案)。
- **响应**:对比特 **2xx** 须在持久化或可靠入队**之后**。
- **安全**:验签用平台侧实现;**禁止** JNI SDK。
- **观测**`traceparent` / `X-Request-Id` 贯通。
---
## 4. 风险与对前端/SDK
| 风险 | 缓解 |
|------|------|
| 契约漂移 | I3 起 OpenAPI SSOTfixture 入库 |
| 幂等与 UX | DB 唯一约束;前端按业务 id 去重 |
| I5 前 Webhook 空转 | I1I4 以契约+假实现为主 |
| SDK 误用 POM | CI `dependency:tree` grep |
| 前端滞后 | I5 起 Postman/Scalar 先行验收 |
---
## 5. CI Jobs 建议
| Job | 步骤摘要 |
|-----|----------|
| `platform-build` | `mvn -pl platform-bootstrap -am verify`;可选禁止 `craftlabs-auth-bitanswer` |
| `webhook-build` | 同上 `webhook-bootstrap` |
| `platform-integration-test` | Testcontainers + FlywayI5+ Mock Callback 全链路 |
| `cross-service-it`I5+ | Compose:双 JAR + DB + MQ |
---
## 6. V1.1I7I8
| 迭代 | 平台 | Webhook |
|------|------|---------|
| I7 | 按钮级权限码、导出脱敏、限流、安全头 | 速率限制、payload 上限 |
| I8 | 业务指标 RED、批量导入幂等 | Callback 失败率、DLQ 深度 |
---
## 7. MidI9I13)与 V2.0
- **Mid**M7 设备;M8 待办/通知;M9 报表;M11 SSO/并发/强制下线;M2 变更可选 I13。
- **V2.0**M10 举证包;MFA、SECURITY_ADMIN、数据范围;CRM、读模型/CQRS 轻量。
---
## 8. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 由并行 Task 产出并入库;与 Roadmap I1I6 对齐。 |
@@ -0,0 +1,86 @@
# 轨道 Bdelivery-platform-uiVue 3)— 并行实施包
> **对齐**[BPM 排期 §7](../chuangfei-platform-bpm-and-roadmap.md) · [功能模块 M1M11](../../chuangfei-platform-product-modules.md) · [并行索引](../PARALLEL_ITERATION_INDEX.md)
---
## 1. 技术基线
| 项 | 选型 |
|----|------|
| 运行时 | **Vue 3** + **Vite**Composition API + `<script setup>` |
| 状态 | **Pinia** |
| 路由 | **vue-router**(懒加载、meta:权限码、`title` |
| HTTP | **axios**(拦截器:JWT Bearer 或 Session Cookie |
| UI | **Element Plus** |
| 契约(可选) | OpenAPI → TS 类型,与后端 **contract-first** |
认证:环境切换 `withCredentials``Authorization`;登录后写入 Pinia。
---
## 2. I1I6 前端 Backlog
| 迭代 | 路由/页面 | 关键组件 | API/状态 | E2E | DoD |
|------|-----------|----------|----------|-----|-----|
| **I1** | `/login``/` 布局、`/403`/`/404` | `AppLayout``LoginForm``IdleTimeout` | auth/user store401 统一处理 | P0 登录与回跳 | RBAC 路由守卫;菜单按权限过滤 |
| **I2** | `/customers``/projects` 及详情;`/admin/dictionaries` | `DataTable``CustomerForm``ProjectForm` | CRUD + 字典缓存 | P0 客户→项目 | 与 M1 P0 字段一致 |
| **I3** | `/contracts`、新建向导、`/contracts/:id` | `ContractWizard``ContractLineEditor``StatusTag` | 状态机由后端校验,前端禁用非法操作 | P0 草稿→生效 | M2 P0M10-F01 入口 |
| **I4** | `/deliveries``/licenses/sn`、导入 | `DeliveryBatchForm``SnBindDialog``SnStatusTimeline` | 交付与合同行;孤儿 SN 警告 | P0 交付→SN→回写 | M3/M4 P0 |
| **I5** | `/callbacks``/integration/environments``product-lines` | `CallbackInboxTable``CallbackPayloadViewer`(脱敏) | Inbox 处置;M6 只读/受限写 | P0 列表→详情→状态 | 与 Webhook 联调或 staging |
| **I6** | 全链路导航与修缺陷(参见 [I6_CLOSEOUT.md](../iterations/I6_CLOSEOUT.md) | 可选 `GlobalSearch` | 错误与空态统一;生产 `VITE_API_BASE` | P0 **BP-0106+11** 全链路 E2E | UAT 无 P0;手册截图一致 |
| **I7** | `/callbacks*`**OPS/SYS_ADMIN**;菜单与首页按角色裁剪 | Pinia `hasAnyRole` | 与 `@PreAuthorize` 对齐;`ops/ops` 演示 | 与 Webhook 异步投递联调 | 参见 [I7_DESIGN.md](../iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](../iterations/I7_IMPLEMENTATION_REVIEW.md) |
---
## 3. 页面 ↔ 模块(摘要)
| 模块 | 典型路由 | MVP 迭代 |
|------|----------|----------|
| M11 | 登录、用户/角色(I2)、Mid SSO | I1、Mid |
| M1 | 客户、项目 | I2 |
| M2 | 合同 | I3 |
| M3/M4 | 交付、SN | I4 |
| M5/M6 | Callback、集成配置 | I5 |
| M7M9 | 设备、待办、报表 | Mid |
| M10 | 审计展示/导出 | I3+ / Mid / V2 |
---
## 4. Mock 与契约先行
| 工作包 | 可先 mock | 建议 |
|--------|-------------|------|
| I1 壳层 + RBAC | ✅ | MSW / vite-plugin-mock |
| M1 CRUD | ✅ | I3 前客户/项目 DTO 冻结 |
| M2 合同 | ⚠️ | 状态迁移 **OpenAPI 冻结**I2 末) |
| M3/M4 | ⚠️ | 门禁与 M11-F20 契约先行 |
| M5/M6 | ⚠️ | Payload 以 Webhook/API DTO 为准 |
**契约顺序**Auth → Customer/Project → Contract → Delivery/SN → Callback/Integration。
---
## 5. V1.1 / Mid / V2.0(摘要)
- **I7I8**:按钮级 `v-permission`;导出脱敏;运维只读仪表盘;批量导入进度。
- **Mid I9~I13**:设备/换机;待办与通知配置;对账与 Callback 报表;**SSO**`/oauth/callback` 等);合同变更对比可选。
- **V2.0**M10 导出包、MFA、SECURITY_ADMIN、数据范围、CRM 同步状态页。
---
## 6. E2E 建议
| 层级 | 工具向 |
|------|--------|
| Smoke | Playwright / Cypress:登录 + 各迭代主路由 |
| P0 业务 | I6 全链路 + V1.1 导入导出 |
| Mid | SSO、待办、报表(staging IdP 或 mock OIDC |
---
## 7. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 由并行 Task 产出并入库。 |
+90
View File
@@ -0,0 +1,90 @@
# 轨道 Ccraftlabs-authorization-sdk(客户端)— 并行实施包
> **对齐**[BPM §7 BP-10](../chuangfei-platform-bpm-and-roadmap.md) · [工程布局 §5](../WORKSPACE_ENGINEERING_LAYOUT.md)
> **事实**`craftlabs-auth-core` / `bitanswer` / `selfhosted``schemas/``examples/`;平台 **不嵌入** Native。
---
## 1. SDK 版本线与 Native 标签
| 原则 | 说明 |
| ------------- | ---------------------------------------------------------------- |
| **独立 SemVer** | SDK **不与**平台 Fat JAR 版本号绑定;对外话术分两条线。 |
| **MAJOR** | Schema 不兼容、公共 API 破坏、JNI 契约不兼容。 |
| **MINOR** | 向后兼容扩展:可选 Schema 字段、新 API、新示例。 |
| **PATCH** | Bugfix、文档;无契约变化。 |
| **预发布** | `-rc.N` 与平台 UAT/比特联调对齐;**不用**平台版本代替 SDK 版本。 |
| **Native** | 每次发行 **JAR + 各 OS native** 同版本;Git **单 tag**(如 `v1.4.2`);禁止只升其一。 |
---
## 2. 平台迭代 × SDK 工作对照
| 迭代 | 平台焦点 | SDK **必需** | SDK **非阻塞** |
| ------ | ------------------- | ------------------------------------------ | ---------------------- |
| **I1** | M11 脚手架 | 无 | 文档:与平台产物区分 |
| **I2** | M1 | 无 | 文档 |
| **I3** | M2、M10-F01 | 无 | 文档 |
| **I4** | M3、M4、激活回写 | 烟测路径下 **推荐 PATCH** 修缺陷 | 文档/示例与 BP-10 口径 |
| **I5** | M5、M6、Webhook、BP-10 | **Schema + `AuthConfigs` + examples 同步** | Native 与 Webhook 无关 |
| **I6** | UAT | **冻结 SDK 版本**、CHANGELOG、**BitAnswer 兼容矩阵** | 禁止 UAT 周做 MAJOR Schema |
**小结**:I1~I4 以文档/示例为主;**I5 起** Schema/Java/示例为硬交付;**I6** 冻结与清单。
---
## 3. BP-10:何时 bump Schema / Java / 文档 / 示例
| 变更类型 | Schema | Java AuthConfigs | docs | examples |
| ------------------- | ------------------- | ---------------- | ---- | -------- |
| 新增可选字段 | MINOR(或 PATCH | 同交付对齐 | 字段说明 | 可选示例 |
| 新增必填/收紧/改名 | MAJOR 或 breaking 策略 | 必须同步 | 迁移说明 | 必须更新模板 |
| 仅 description | PATCH | 通常无 | 可选 | 可选 |
| M6 仅改发布流程、JSON 形态不变 | 无 | 无 | 手册可选 | 无 |
**硬规则**:影响「平台导出 → Schema → 客户端」任一环的变更,**同一发布列车** 更新 Schema + Java(若有)+ 示例。
---
## 4. SDK 发布检查清单(非平台 Fat JAR)
1. `mvn -f java/pom.xml clean verify`Native 各 OS 构建通过。
2. `mvn deploy``craftlabs-auth-*` **版本一致****无** `spring-boot-maven-plugin` repackage。
3. Native 与 JAR **同版本** 发布。
4. Schema + examples CI 校验。
5. CHANGELOG:破坏性变更、BitAnswer 版本区间、已知平台导出版本(若可公开)。
6. **兼容矩阵**:SDK 版本、厂商库、OS/JDK;与平台应用版本 **分列**
7. Git tag**不含** `java -jar platform.jar` 说明。
---
## 5. V1.1+ SDK 增强(规划)
| 主题 | 说明 |
| --------------------- | ---------------------------------------- |
| 更严校验 | CI 对 examples 与 Schema 双向校验;平台脱敏 fixture |
| Fixtures | 最小/完整/非法 JSON 绑定 Schema 版本 |
| 可选 `config-model` 薄模块 | 供平台后端复用 POJO **无 native**;另案评审 |
---
## 6. 防混淆话术
**SDK** = `craftlabs-auth-*` **库** + **Native** + Schema/示例;**平台** = 独立仓 **bootstrap Fat JAR**,无 BitAnswer Native**版本号线独立**。并行以 **I5BP-10** 为契约首要对齐点,**I6** 为 SDK 冻结点。
---
## 7. 修订记录
| 日期 | 说明 |
| ---------- | --------------- |
| 2026-04-06 | 由并行 Task 产出并入库。 |
+62 -10
View File
@@ -1,12 +1,11 @@
# 比特授权云 · Java 语言接口(离线摘录)
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:<https://doc.bitanswer.cn/docs/client-api/java-interface-v2/>
> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:[https://doc.bitanswer.cn/docs/client-api/java-interface-v2/](https://doc.bitanswer.cn/docs/client-api/java-interface-v2/)
---
## 构造方法详细信息
### BitAnswer
```java
@@ -16,7 +15,6 @@ public BitAnswer(String url, String sn, LoginMode mode)
## 认证
### login / loginEx
```java
@@ -30,6 +28,7 @@ void loginEx (String url,
String xmlScope,
LoginMode mode)
```
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
Login 等价于 LoginExfeatureId=0);当需要登录包含指定特征项的授权时才需要调用LoginEx。
@@ -75,6 +74,7 @@ Login 等价于 LoginExfeatureId=0);当需要登录包含指定特征项
void loginByToken (String url,
String token)
```
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
### 参数
@@ -95,6 +95,7 @@ void loginByTokenEx (String url,
String idpGuid,
String grantType)
```
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
### 参数
@@ -118,6 +119,7 @@ void loginByPassword (String url,
String account,
String password)
```
通过用户名和密码登录帐号授权。
### 参数
@@ -137,6 +139,7 @@ void loginByPassword (String url,
```java
void logout()
```
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
### 参数
@@ -152,6 +155,7 @@ void logout()
```java
String revoke (String sn)
```
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
### 参数
@@ -168,6 +172,7 @@ String revoke (String sn)
void revokeOnline (String url,
String sn)
```
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。本函数需要进行网络连接。
### 参数
@@ -184,6 +189,7 @@ void revokeOnline (String url,
```java
void removeSn (String sn)
```
删除指定授权码在本机的授权数据。
### 参数
@@ -199,6 +205,7 @@ void removeSn (String sn)
```java
int heartbeat()
```
手动心跳,可以无限次调用,10s只会触发一次。
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
@@ -218,6 +225,7 @@ String sessionControl (String url,
String sessionId,
SessionCtlType type)
```
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
### 参数
@@ -243,6 +251,7 @@ String sessionControl (String url,
void setSessionState (int state,
byte[] pReserved)
```
设置客户端的状态为空闲状态或繁忙状态或激活状态。
### 参数
@@ -264,13 +273,13 @@ void setSessionState (int state,
## 激活升级
### updateOnline
```java
void updateOnline (String url,
String sn)
```
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
### 参数
@@ -288,6 +297,7 @@ void updateOnline (String url,
String getRequestInfo (String sn,
BindingType type)
```
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。
### 参数
@@ -310,6 +320,7 @@ String getRequestInfo (String sn,
```java
String applyUpdateInfo (String updateInfo)
```
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
### 参数
@@ -327,6 +338,7 @@ String getUpdateInfo (String url,
String sn,
String requestInfo)
```
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
### 参数
@@ -341,12 +353,12 @@ String getUpdateInfo (String url,
## 特征项操作
### batchBegin
```java
void batchBegin (BatchMode mode)
```
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_batchEnd接口批量提交连接集团服务。
### 参数
@@ -365,6 +377,7 @@ void batchBegin (BatchMode mode)
```java
int[] batchEnd()
```
批量提交Query请求。
### 参数
@@ -380,6 +393,7 @@ int[] batchEnd()
```java
int queryFeature (int featureId)
```
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
### 参数
@@ -395,6 +409,7 @@ int queryFeature (int featureId)
```java
int releaseFeature (int featureId)
```
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
### 参数
@@ -413,6 +428,7 @@ int queryFeatureEx (int featureId,
int required,
String scope)
```
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
### 参数
@@ -439,6 +455,7 @@ int releaseFeatureEx (int featureId,
int consumed,
String scope)
```
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
### 参数
@@ -459,6 +476,7 @@ long queryFeatureEx2 (String featureName,
int required,
String scope)
```
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
### 参数
@@ -486,6 +504,7 @@ long queryFeatureEx2 (String featureName,
void releaseFeatureEx2 (byte[] ticket,
int consumed)
```
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
### 参数
@@ -503,6 +522,7 @@ void releaseFeatureEx2 (byte[] ticket,
int getFeatureInfo2 (String featureName,
String scope)
```
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
### 参数
@@ -520,6 +540,7 @@ int getFeatureInfo2 (String featureName,
String getFeatureInfoEx2 (String featureName,
String scope)
```
获取指定feature的信息,以XML格式返回。
### 参数
@@ -544,6 +565,7 @@ String getFeatureInfoEx2 (String featureName,
String getTicketInfo (byte[] ticket,
int type)
```
获取ticket信息。
### 参数
@@ -568,6 +590,7 @@ int convertFeature (int featureId,
int para3,
int para4)
```
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
### 参数
@@ -588,6 +611,7 @@ int convertFeature (int featureId,
byte[] encryptFeature (int featureId,
byte[] pPlainBuffer)
```
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
### 参数
@@ -605,6 +629,7 @@ byte[] encryptFeature (int featureId,
byte[] decryptFeature (int featureId,
byte[] pCipherBuffer)
```
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
### 参数
@@ -621,6 +646,7 @@ byte[] decryptFeature (int featureId,
```java
int readFeature (int featureId)
```
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
### 参数
@@ -637,6 +663,7 @@ int readFeature (int featureId)
void writeFeature (int featureId,
int featureValue)
```
更新“读写”类型的特征项的数据内容。
### 参数
@@ -650,12 +677,12 @@ void writeFeature (int featureId,
## 配置项操作
### getDataItem
```java
byte[] getDataItem (String dataItemName)
```
读取指定的配置项数据。
### 参数
@@ -672,6 +699,7 @@ byte[] getDataItem (String dataItemName)
void setDataItem (String dataItemName,
byte[] dataItemValue)
```
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
### 参数
@@ -688,6 +716,7 @@ void setDataItem (String dataItemName,
```java
int getDataItemNum()
```
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
### 参数
@@ -703,6 +732,7 @@ int getDataItemNum()
```java
String getDataItemName (int index)
```
根据配置项索引获取其名称,一般用于配置项的枚举操作。
### 参数
@@ -718,6 +748,7 @@ String getDataItemName (int index)
```java
void removeDataItem (String dataItemName)
```
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
### 参数
@@ -730,12 +761,12 @@ void removeDataItem (String dataItemName)
## 信息获取
### getSessionInfo
```java
String getSessionInfo (SessionType type)
```
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
### 参数
@@ -771,6 +802,7 @@ String getSessionInfo (SessionType type)
String getInfo (String sn,
InfoType type)
```
获取本地授权信息。
### 参数
@@ -800,6 +832,7 @@ String getServerInfo (String url,
String scope,
ServerInfoType type)
```
获取集团服务的license信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
### 参数
@@ -824,6 +857,7 @@ String getServerInfo (String url,
```java
int getVersion()
```
获取客户端安全库版本号。
### 参数
@@ -841,6 +875,7 @@ void testBitService (String url,
String sn,
int featureId)
```
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
### 参数
@@ -858,6 +893,7 @@ void testBitService (String url,
```java
String getProductPath()
```
获取授权存储目录。
### 参数
@@ -873,6 +909,7 @@ String getProductPath()
```java
int getLastError()
```
获取上一个API调用返回的错误码。
### 参数
@@ -889,13 +926,13 @@ int getLastError()
## 属性设置
### setAttr
```java
void setAttr (int type,
byte[] pValue)
```
设置全局配置或当前会话的配置。
### 参数
@@ -924,6 +961,7 @@ void setProxy (String hostName,
String userId,
String password)
```
设置代理服务的地址和端口。
### 参数
@@ -943,6 +981,7 @@ void setProxy (String hostName,
void setCustomInfo (int infoId,
String infoData)
```
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
### 参数
@@ -958,6 +997,7 @@ void setCustomInfo (int infoId,
void setCustomInfo (int infoId,
int infoData)
```
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
### 参数
@@ -980,6 +1020,7 @@ void setCustomInfo (int infoId,
```java
void setRootPath (String rootPath)
```
设置授权文件的存储路径。
### 参数
@@ -997,6 +1038,7 @@ void setLocalServer (String hostName,
int port,
int timeout)
```
设置集团服务的地址和端口。
### 参数
@@ -1011,13 +1053,13 @@ void setLocalServer (String hostName,
## 借出操作
### getBorrowRequest
```java
String getBorrowRequest (String sn,
int durationDay)
```
获取借出请求串,用来离线借出。
### 参数
@@ -1035,6 +1077,7 @@ String getBorrowRequest (String sn,
String getBorrowFeatureRequest (int durationDay,
String borrowScope)
```
获取借出请求串,该接口支持指定特征项借出。
### 参数
@@ -1056,6 +1099,7 @@ String getBorrowFeatureRequest (int durationDay,
void checkOutSn (String url,
int featureId)
```
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1075,6 +1119,7 @@ void checkOutSnEx (String url,
String xmlScope,
int durationDays)
```
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1096,6 +1141,7 @@ void checkOut (String url,
String featureList,
int durationDays)
```
从授权服务器在线借出授权码或者特征项。
### 参数
@@ -1113,6 +1159,7 @@ void checkOut (String url,
type:借出类型。目前仅支持ws,表示从外网借出(针对云授权和集团授权)
sn:指定借出的SN
```
- **featureList** - [IN] 特征项列表,指定借出的特征项列表,如果不传则借出整个SN。示例:
```xml
@@ -1125,6 +1172,7 @@ sn:指定借出的SN
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
```
- **durationDays** - [IN] 借出有效期,单位为天,需小于等于授权码允许的最大借出天数。
### 返回
@@ -1138,6 +1186,7 @@ void checkOutFeatures (String url,
int[] featureList,
int durationDays)
```
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
### 参数
@@ -1156,6 +1205,7 @@ void checkOutFeatures (String url,
void checkIn (String url,
int featureId)
```
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1174,6 +1224,7 @@ void checkInEx (String url,
int featureId,
String xmlScope)
```
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1191,6 +1242,7 @@ void checkInEx (String url,
```java
void ApplyBorrowInfo (string borrowInfo)
```
客户端应用离线借出串,ApplyUpdateInfo可以兼容该接口。
### 参数
@@ -1199,4 +1251,4 @@ void ApplyBorrowInfo (string borrowInfo)
### 返回
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,575 @@
# 自研授权 SDK 实施计划(完整版)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 实现自研软件授权 SDKRust native + Java SDK + 平台签发后端),与比特安索双线共存,Provider 可扩展架构。
**Architecture:** 单 Rust cdylibcraftlabs_auth_core)通过 Provider trait 路由。许可证 AES-256-GCM 加密载荷 + RSA-256 签名。签发走 delivery-platform-api:8080SDK 在线交互走 license-webhook-ingress:8081。
**Tech Stack:** Rust (craft-core, cdylib/staticlib), Java 17 (Maven), Spring Boot 3.4.x, PostgreSQL 15 + MyBatis-Plus, Flyway
**Reference Spec:** `docs/superpowers/specs/2026-05-18-selfhosted-licensing-sdk-design.md`
---
## Phase 0-1 速览(Phase 1 完整步骤见下方任务列表)
Phase 0(构建准备)+ Phase 1(离线核心:Rust crypto/device/cache/license 模块 + trait_provider + lib.rs 重构 + Java 配置扩展 + Schema + 数据库迁移 + LicenseSigner + LicenseController)共计 15 个任务。
---
## Task 0.1: 更新 Cargo.toml
**Files:** Modify `native/craft-core/Cargo.toml`
- [ ] **Step 1: 重命名 lib + 添加依赖**
```toml
[lib]
crate-type = ["cdylib", "staticlib"]
name = "craftlabs_auth_core"
[dependencies]
obfstr = "0.4"
sha2 = "0.10"
libloading = "0.8"
once_cell = "1"
rsa = { version = "0.9", features = ["sha2"] }
aes-gcm = "0.10"
hkdf = "0.12"
base64 = "0.22"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { version = "0.52", features = ["Win32_System_Diagnostics_Debug"] }
[build-dependencies]
sha2 = "0.10"
[features]
default = ["security-hardening"]
security-hardening = []
```
- [ ] **Step 2: `cargo check --manifest-path native/craft-core/Cargo.toml`**
- [ ] **Step 3: `git commit -m "build(native): rename lib to craftlabs_auth_core, add selfhosted deps"`**
---
## Task 1.1-1.6: Rust 离线核心模块(Part 1 覆盖)
以下 6 个任务的完整代码见 Part 1(文档长度原因拆分)。Part 1 commit 记录:
| 任务 | 文件 | 内容 |
|------|------|------|
| 1.1 | `error.rs` | 扩展错误码(加密/签名/许可证状态) |
| 1.2 | `crypto.rs` | HKDF 密钥派生 + AES-256-GCM 加解密 + RSA-SHA256 验签 |
| 1.3 | `device.rs` | 4 层硬件指纹采集(Linux DMI/machine-id/FS/MAC),stability_score |
| 1.4 | `provider_selfhosted/cache.rs` | 本地加密缓存(AES 加密写入磁盘 + 心跳 checkpoint |
| 1.5 | `provider_selfhosted/license.rs` | 验签→解密→时间校验→离线宽限期 |
| 1.6 | `provider_selfhosted/mod.rs` | SelfHostedProvider struct + 初始化/离线校验/hasFeature |
> Part 1 详细步骤已写入 Gitcommit `d7469af`spec)及之前。
---
## Task 1.7: trait_provider.rsProvider trait + 路由)
**Files:** Create `native/craft-core/src/trait_provider.rs`
- [ ] **Step 1: 创建 trait_provider.rs**
```rust
use std::collections::HashMap;
use crate::{CraftContext, LicenseInfo, error::LicenseError};
pub struct LicenseStatus {
pub licensed: bool,
pub not_after: Option<i64>,
pub features: HashMap<String, bool>,
pub device_count: u32,
pub max_devices: u32,
pub heartbeat_due: Option<i64>,
}
pub struct ActivateResponse {
pub success: bool,
pub device_id: String,
pub license_payload: Vec<u8>,
}
pub struct HeartbeatResponse {
pub valid: bool,
pub lease_until: Option<i64>,
pub update_available: bool,
pub new_license_payload: Option<Vec<u8>>,
}
pub trait Provider: Send + Sync {
fn initialize(&mut self, ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError>;
fn activate(&self, ctx: &CraftContext, license_key: &str) -> Result<ActivateResponse, LicenseError>;
fn check_license(&self, ctx: &CraftContext) -> Result<LicenseStatus, LicenseError>;
fn heartbeat(&self, ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError>;
fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool;
fn release(&mut self, ctx: &CraftContext) -> Result<(), LicenseError>;
fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfo;
fn close(&mut self);
}
```
- [ ] **Step 2: `cargo check` → `git commit -m "feat(rust): add Provider trait abstraction"`**
---
## Task 1.8: 重构 lib.rsC ABI 路由到 Provider trait
**Files:** Modify `native/craft-core/src/lib.rs`
- [ ] **Step 1: 替换 lib.rs**
```rust
use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;
mod trait_provider;
mod error;
mod crypto;
mod device;
mod provider_selfhosted;
mod security;
mod session;
pub use trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus};
pub use error::LicenseError;
pub struct CraftContext {
pub provider: Option<Box<dyn Provider>>,
pub initialized: bool,
}
#[repr(C)] pub struct CraftResult { pub success: i32, pub message: *const c_char }
#[repr(C)]
pub struct LicenseInfo {
pub is_licensed: i32,
pub expiration_date: i64,
pub feature_names: *const *const c_char,
pub feature_values: *const i32,
pub feature_count: i32,
}
impl CraftContext { pub fn new() -> Self { CraftContext { provider: None, initialized: false } } }
unsafe fn c_str_to_string(ptr: *const c_char) -> String {
if ptr.is_null() { String::new() } else { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
}
static OK_MSG: &[u8] = b"ok\0";
fn ok_result() -> CraftResult { CraftResult { success: 1, message: OK_MSG.as_ptr() as *const c_char } }
fn craft_fail() -> CraftResult {
static FAIL: &[u8] = b"failure\0";
CraftResult { success: 0, message: FAIL.as_ptr() as *const c_char }
}
fn parse_provider(config: &str) -> String {
serde_json::from_str::<serde_json::Value>(config).ok()
.and_then(|v| v.get("provider").and_then(|p| p.as_str().map(|s| s.to_string())))
.unwrap_or_else(|| "selfhosted".to_string())
}
#[no_mangle]
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
let config_str = unsafe { c_str_to_string(config_json) };
let mut ctx = Box::new(CraftContext::new());
let mut provider: Box<dyn Provider> = match parse_provider(&config_str).as_str() {
"selfhosted" => Box::new(provider_selfhosted::SelfHostedProvider::new()),
_ => Box::new(provider_selfhosted::SelfHostedProvider::new()),
};
if let Err(_) = provider.initialize(&ctx, &config_str) {
ctx.initialized = false;
} else {
ctx.provider = Some(provider);
ctx.initialized = true;
}
Box::into_raw(ctx)
}
#[no_mangle]
pub extern "C" fn craft_activate(handle: *mut CraftContext, license_key: *const c_char, _: *const c_char) -> CraftResult {
if handle.is_null() { return craft_fail(); }
let ctx = unsafe { &*handle };
let key = unsafe { c_str_to_string(license_key) };
ctx.provider.as_ref().and_then(|p| p.activate(ctx, &key).ok()).map_or_else(craft_fail, |_| ok_result())
}
#[no_mangle]
pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult {
if handle.is_null() { return craft_fail(); }
let ctx = unsafe { &*handle };
ctx.provider.as_ref().and_then(|p| p.check_license(ctx).ok())
.map_or_else(craft_fail, |s| if s.licensed { ok_result() } else { craft_fail() })
}
#[no_mangle]
pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo {
if handle.is_null() { return ptr::null_mut(); }
let ctx = unsafe { &*handle };
ctx.provider.as_ref().map(|p| p.get_license_info(ctx))
.map_or(ptr::null_mut(), |info| Box::into_raw(Box::new(info)))
}
#[no_mangle] pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
if !info.is_null() { unsafe { drop(Box::from_raw(info)); } }
}
#[no_mangle]
pub extern "C" fn craft_has_feature(handle: *mut CraftContext, feature_name: *const c_char) -> i32 {
if handle.is_null() { return 0; }
let ctx = unsafe { &*handle };
let name = unsafe { c_str_to_string(feature_name) };
ctx.provider.as_ref().map_or(0, |p| if p.has_feature(ctx, &name) { 1 } else { 0 })
}
#[no_mangle] pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
if handle.is_null() { return craft_fail(); }
let ctx = unsafe { &mut *handle };
ctx.provider.as_mut().and_then(|p| p.release(ctx).ok());
ok_result()
}
#[no_mangle] pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
if handle.is_null() { return craft_fail(); }
let ctx = unsafe { &*handle };
ctx.provider.as_ref().and_then(|p| p.heartbeat(ctx).ok()).map_or_else(craft_fail, |_| ok_result())
}
#[no_mangle]
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
if !handle.is_null() {
unsafe { let ctx = &mut *handle; if let Some(ref mut p) = ctx.provider { p.close(); } drop(Box::from_raw(handle)); }
}
}
```
- [ ] **Step 2: 在 provider_selfhosted/mod.rs 追加 Provider trait 实现**
```rust
use crate::trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus};
impl Provider for SelfHostedProvider {
fn initialize(&mut self, _ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError> {
let cfg: serde_json::Value = serde_json::from_str(config_json).map_err(|_| LicenseError::InvalidFormat("config"))?;
let sh = cfg.get("selfhosted").ok_or(LicenseError::ConfigMissing("selfhosted"))?;
let base_url = sh.get("baseUrl").and_then(|v| v.as_str()).unwrap_or("").to_string();
let tenant_key = sh.get("tenantKey").and_then(|v| v.as_str()).unwrap_or("").to_string();
let ogd = sh.get("offlineGraceDays").and_then(|v| v.as_u64()).unwrap_or(7) as u32;
let hih = sh.get("heartbeatIntervalHours").and_then(|v| v.as_u64()).unwrap_or(24) as u32;
let pubkey = option_env!("CRAFTLABS_SELFHOSTED_PUBKEY").unwrap_or("").to_string();
self.initialize(base_url, tenant_key, ogd, hih, pubkey)
}
fn activate(&self, _ctx: &CraftContext, _lk: &str) -> Result<ActivateResponse, LicenseError> {
Err(LicenseError::Network("online activation not yet implemented".into()))
}
fn check_license(&self, _ctx: &CraftContext) -> Result<LicenseStatus, LicenseError> {
self.check_license_offline()?;
let c = self.cache.license.as_ref().ok_or(LicenseError::NoCachedLicense)?;
Ok(LicenseStatus { licensed: true, not_after: c.not_after, features: c.features.clone(), device_count: 0, max_devices: c.max_devices, heartbeat_due: None })
}
fn heartbeat(&self, _ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError> {
Err(LicenseError::Network("heartbeat not yet implemented".into()))
}
fn has_feature(&self, _ctx: &CraftContext, name: &str) -> bool { self.has_feature_offline(name) }
fn release(&mut self, _ctx: &CraftContext) -> Result<(), LicenseError> { Ok(()) }
fn get_license_info(&self, _ctx: &CraftContext) -> LicenseInfo { self.get_license_info_offline() }
fn close(&mut self) { let _ = self.persist_cache(); }
}
```
- [ ] **Step 3: `cargo check` + `cargo test` → `git commit -m "feat(rust): refactor C ABI to route through Provider trait"`**
---
## Task 1.9-1.10: Java 配置 + Schema 扩展
**Files:** Modify `SelfhostedConfigSection.java`, `FeatureMapping.java`, `schemas/craftlabs-auth-config.schema.json`, `examples/config/school.selfhosted.json`
- [ ] **Step 1: SelfhostedConfigSection 新增字段**
```java
public record SelfhostedConfigSection(
@JsonProperty("baseUrl") String baseUrl,
@JsonProperty("tenantKey") String tenantKey,
@JsonProperty("offlineGraceDays") Integer offlineGraceDays, // ★ 新增
@JsonProperty("heartbeatIntervalHours") Integer heartbeatIntervalHours, // ★ 新增
@JsonProperty("publicKeyPem") String publicKeyPem) { // ★ 新增
public SelfhostedConfigSection {
offlineGraceDays = offlineGraceDays != null ? offlineGraceDays : 7;
heartbeatIntervalHours = heartbeatIntervalHours != null ? heartbeatIntervalHours : 24;
}
}
```
- [ ] **Step 2: FeatureMapping 新增 selfhostedFeatureKey**
```java
public record FeatureMapping(
@JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
@JsonProperty("bitanswerFeatureName") String bitanswerFeatureName,
@JsonProperty("selfhostedFeatureKey") String selfhostedFeatureKey) {} // ★ 新增
```
- [ ] **Step 3: Schema 扩展 selfhosted properties**
```json
"offlineGraceDays": { "type": "integer", "minimum": 0, "maximum": 365, "default": 7 },
"heartbeatIntervalHours": { "type": "integer", "minimum": 1, "maximum": 720, "default": 24 },
"publicKeyPem": { "type": "string" }
```
同步扩展 `features.additionalProperties` 增加 `selfhostedFeatureKey`
- [ ] **Step 4: 更新 `examples/config/school.selfhosted.json`**
- [ ] **Step 5: `mvn compile -pl craftlabs-auth-core` + Schema 校验 → `git commit -m "feat: extend Java config and Schema for selfhosted SDK"`**
---
## Task 1.11: 平台数据库迁移(Flyway V2
**Files:** Create `services/delivery-platform-api/src/main/resources/db/migration/V2__selfhosted_licensing.sql`
核心 6 张表:`platform_license_policies`, `platform_license_keys`, `platform_licenses`, `platform_license_features`, `platform_license_activations`, `platform_license_heartbeats`。完整 DDL 见设计文档 §3.6。
- [ ] **Step: `docker compose up -d postgres` + `mvn flyway:migrate` → commit**
---
## Task 1.12: LicenseSigner.javaRSA 签名 + AES 加密签发)
**Files:** Create `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSigner.java`
```java
@Service
public class LicenseSigner {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String EMBEDDED_SALT = "craftlabs-license-salt-v1-2026-05";
public String sign(SignedLicensePayload payload, PlatformLicenseKey key) throws Exception {
byte[] payloadJson = MAPPER.writeValueAsBytes(payload);
byte[] aesKey = deriveAesKey(EMBEDDED_SALT, payload.getLicenseId());
byte[] encrypted = aesGcmEncrypt(aesKey, payloadJson);
String payloadB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(encrypted);
PrivateKey privateKey = loadPrivateKey(key.getPrivateKey());
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(encrypted);
String sigB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(sig.sign());
LicenseDocument doc = LicenseDocument.builder()
.version(1).licenseId(payload.getLicenseId())
.payload(payloadB64)
.signature(SignatureBlock.builder().algorithm("RS256").keyId(key.getKeyId()).value(sigB64).build())
.build();
return MAPPER.writeValueAsString(doc);
}
private byte[] deriveAesKey(String salt, String licenseId) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(salt.getBytes("UTF-8"), "HmacSHA256"));
byte[] prk = mac.doFinal(new byte[0]);
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
mac.update(licenseId.getBytes("UTF-8"));
mac.update((byte) 0x01);
return mac.doFinal();
}
private byte[] aesGcmEncrypt(byte[] key, byte[] plaintext) throws Exception {
byte[] nonce = new byte[12]; new SecureRandom().nextBytes(nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, nonce));
byte[] ct = cipher.doFinal(plaintext);
byte[] r = new byte[12 + ct.length]; System.arraycopy(nonce, 0, r, 0, 12); System.arraycopy(ct, 0, r, 12, ct.length);
return r;
}
}
```
- [ ] **Step: `mvn compile` → `git commit -m "feat(platform): add LicenseSigner for RSA+AES license issuance"`**
---
## Task 1.13-1.14: Entity/Mapper + LicenseService + LicenseController
**Files:** Create 5 组 Entity+Mapper`persistence/license/`+ LicenseService + LicenseController
核心 API
```
POST /api/v1/licenses → 签发许可证(需 LICENSE_OPS 角色)
GET /api/v1/licenses/{id} → 查询详情
POST /api/v1/licenses/{id}/revoke → 吊销
```
SecurityConfig 新增:`.requestMatchers("/api/v1/licenses/**").hasAnyRole("LICENSE_OPS", "ADMIN")`
- [ ] **Step: `mvn compile` + `mvn test -Dtest=LicenseControllerTest` → commit**
---
## Task 1.15: Phase 1 集成验证
- [ ] **Step 1: 生成 RSA 密钥对**
```bash
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
```
- [ ] **Step 2: `export CRAFTLABS_SELFHOSTED_PUBKEY="$(cat public_key.pem)"`**
- [ ] **Step 3: `cargo test --manifest-path native/craft-core/Cargo.toml` → all pass**
- [ ] **Step 4: 启动服务 + curl 签发 → Rust 离线验签通过**
```bash
curl -X POST http://localhost:8080/api/v1/licenses \
-H "Authorization: Bearer $TOKEN" -d '{"tenantId":"test",...}'
# 将返回的 license.json 保存 → Rust 集成测 validate
```
- [ ] **Step 5: Commit**
---
## Phase 2: 在线激活(3 个任务)
### Task 2.1: protocol.rsHTTP 请求/响应序列化)
**Files:** Create `native/craft-core/src/provider_selfhosted/protocol.rs`
Serialize/Deserialize DTOs`ActivateRequest`, `ActivateResponseBody`, `HeartbeatRequest/Response`, `CheckRequest/Response`, `ReleaseRequest/Response`。HMAC 签名工具函数 `build_hmac_signature`
新增依赖:`reqwest = "0.12"`, `hex = "0.4"`, `hmac = "0.12"`, `rand = "0.8"`, `tokio = "1"`
- [ ] **Step: `cargo check` → commit**
### Task 2.2: activate.rs(在线激活 HTTPS 请求)
**Files:** Create `native/craft-core/src/provider_selfhosted/activate.rs`
```rust
pub async fn online_activate(config: &SelfHostedConfig, fp: &DeviceFingerprint, license_key: &str)
-> Result<ActivateResponse, LicenseError>
{
let req = protocol::ActivateRequest { license_key: license_key.to_string(), device_fingerprint: /* fp */ };
let body = serde_json::to_string(&req).unwrap();
// POST /license/v1/activate + HMAC headers ...
match resp.status() {
200 => Ok(parse_body),
409 => Err(DeviceLimitReached),
403 => Err(LicenseRevoked),
}
}
```
`provider_selfhosted/mod.rs``activate()` 中调用 `tokio::runtime::Handle::current().block_on(online_activate(...))`
- [ ] **Step: `cargo test` → commit**
### Task 2.3: Webhook 侧 LicenseController + ActivateService
**Files:** Create license endpoints in `services/license-webhook-ingress`
- `NonceValidator.java`: Nonce 去重(ConcurrentHashMap + 5min 窗口)
- `LicenseActivateService.java`: 查许可证状态 + 终端配额 + 设备匹配 + 下发生效
- `LicenseController.java`: `POST /license/v1/activate`Bearer tenantKey + HMAC header 校验)
- [ ] **Step: `mvn compile` + `mvn test` → commit**
---
## Phase 3: 心跳 + 离线兜底(2 个任务)
### Task 3.1: heartbeat.rs(在线心跳 + 吊销检测)
**Files:** Create `native/craft-core/src/provider_selfhosted/heartbeat.rs`
```rust
pub async fn online_heartbeat(config: &SelfHostedConfig, device_hash: &str, license_key: &str)
-> Result<HeartbeatResponse, LicenseError>
{
// POST /license/v1/heartbeat →
// 200: 更新租约 + 可选下发新许可证
// 410: LicenseRevoked
}
```
更新 mod.rs heartbeat() 实现。
### Task 3.2: Webhook HeartbeatService + CheckService + ReleaseService
**Files:** Append to webhook `LicenseController`
- `POST /license/v1/heartbeat` — 更新 `last_heartbeat`,返回租约续期时间
- `POST /license/v1/check` — 在线校验许可证有效性
- `POST /license/v1/release` — 释放终端占用
---
## Phase 4: 完善与生产加固(5 个任务)
### Task 4.1: build.rs 嵌入 RSA 公钥
**Files:** Modify `native/craft-core/build.rs`, Create `native/craft-core/embedded/pubkey.pem`
```rust
// build.rs
fn main() {
let pubkey = fs::read_to_string("embedded/pubkey.pem").unwrap_or_default();
println!("cargo:rustc-env=CRAFTLABS_SELFHOSTED_PUBKEY={}", pubkey.trim());
println!("cargo:rerun-if-changed=embedded/pubkey.pem");
}
```
将生产公钥放入 `embedded/pubkey.pem`(不提交私钥)。
### Task 4.2: SelfHostedAuthProvider 加载正确库
**Files:** Modify `java/craftlabs-auth-selfhosted/.../SelfHostedAuthProvider.java`
`System.loadLibrary("craftlabs_auth_bitanswer")``System.loadLibrary("craftlabs_auth_core")`
### Task 4.3: MultiProviderSmokeTest
**Files:** Create `java/craftlabs-auth-tests/.../MultiProviderSmokeTest.java`
测试:初始化 selfhosted → 离线校验 → close → 初始化 bitanswer → close,双线不冲突。
### Task 4.4: CI 适配
- `ci-native.yml`: 更新 artifact name,确保 `embedded/pubkey.pem` 存在
- `ci-platform.yml`: 新增 license 模块测试 Job
### Task 4.5: 最终集成验证
```bash
cargo test --manifest-path native/craft-core/Cargo.toml # Rust 全量
mvn -f java/pom.xml verify # Java SDK
mvn -f services/pom.xml verify # 平台
docker compose -f services/docker-compose.yml up -d postgres # 起库
# curl 签发 → Rust 离线验签 → curl 在线激活 → 终端满 409 → 吊销 410
```
---
## 总结
| Phase | 任务数 | 核心交付 |
|-------|--------|----------|
| P0 | 1 | Cargo.toml 依赖 |
| P1 | 14 | Rust 离线核心 + Java 配置 + 平台签发后端 |
| P2 | 3 | 在线激活(Rust HTTPS + Webhook 端点) |
| P3 | 2 | 心跳 + 离线兜底 + 吊销检测 |
| P4 | 5 | build.rs 嵌入 + 测试 + CI |
| **合计** | **25** | |
@@ -0,0 +1,282 @@
# 剩余缺口 WBS 任务拆解与排期计划
> **基于**:PRD vs 代码实现状态完整审计(2026-05-25)
> **范围**:全部 ⚠️ 部分实现 + ❌ 未实现 功能点
> **涉及**:平台后端/前端 + 客户端 SDK
---
## 迭代路线图
| 迭代 | 周期 | 聚焦 | 任务数 | 估计工时 |
|------|------|------|--------|---------|
| **I14** | T0+1W | P0 缺口修复 + 审计修复 | 7 | 12h |
| **I15** | T0+2W | P1 增强(M2/M3/M4 | 6 | 14h |
| **I16** | T0+3W | P1 增强(M5/M6/M8/M11 | 8 | 16h |
| **I17** | T0+4W | SDK JNI 桥接 + 单元测试 | 3 | 16h |
| **V2.1** | T0+6W | P2 功能 + 其他语言封装 | 8 | 20h |
---
## 迭代 I14P0 缺口修复(12h
### I14-T1: M1-F06 项目干系人(2h
**文件:**
- Create: `services/.../db/migration/V17__project_stakeholder.sql`
- Create: `services/.../persistence/project/PlatformProjectStakeholder.java`
- Create: `services/.../persistence/project/PlatformProjectStakeholderMapper.java`
- Create: `services/.../web/dto/StakeholderRequest.java`
- Create: `services/.../web/dto/StakeholderResponse.java`
- Modify: `services/.../project/ProjectController.java`
- Modify: `services/.../service/ProjectService.java`
- Create: `web/.../views/ProjectStakeholderDialog.vue`
- Modify: `web/.../views/ProjectsView.vue`
**DB:**
```sql
CREATE TABLE platform_project_stakeholder (
id BIGSERIAL PRIMARY KEY,
project_id BIGINT NOT NULL REFERENCES platform_project(id),
contact_name VARCHAR(128) NOT NULL,
contact_role VARCHAR(64),
phone VARCHAR(32),
email VARCHAR(128),
is_internal BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
**工作量:** 后端 2h + 前端 1h
---
### I14-T2: M2-F01 合同日期字段(1h
**文件:**
- Create: `services/.../db/migration/V18__contract_date_fields.sql`
- Modify: `services/.../persistence/contract/PlatformContract.java`
- Modify: `services/.../web/dto/ContractCreateRequest.java`
- Modify: `services/.../web/dto/ContractUpdateRequest.java`
- Modify: `services/.../web/dto/ContractResponse.java`
- Modify: `services/.../service/ContractService.java`
- Modify: `services/.../contracts/ContractController.java`
- Modify: `web/.../views/ContractWizardView.vue`
**DB:**
```sql
ALTER TABLE platform_contract
ADD COLUMN signing_date DATE,
ADD COLUMN effective_date DATE,
ADD COLUMN end_date DATE;
```
---
### I14-T3: M2-F04 行项 amount 字段 UI 补全(0.5h
**文件:**
- Modify: `web/.../views/ContractDetailView.vue`(添加 amount字段到行项对话框)
- Modify: `web/.../views/ContractWizardView.vue`(添加 amount到行项表)
---
### I14-T4: M4-F05 激活原因码分类(1.5h
**文件:**
- Modify: `services/.../api/web/dto/LicenseSnStatusPatchRequest.java`
- Modify: `services/.../api/service/LicenseSnService.java`
- Modify: `web/.../views/LicenseSnDetailView.vue`
在状态变更对话框中增加「原因码」下拉字段:ACTIVATION_SUCCESS / ACTIVATION_FAILED / MANUAL_CHANGE / EXPIRED
---
### I14-T5: M10-F02 审计 userId 筛选(1h
**文件:**
- Modify: `services/.../api/service/AuditService.java`searchAuditEvents加userId参数)
- Modify: `services/.../api/audit/AuditController.java`
---
### I14-T6: M11-F05 登录锁定逻辑接入(2h)
**文件:**
- Modify: `services/.../api/auth/AuthController.java`
`AuthController.login()` 中,硬编码用户校验之前插入:
1. 查询 `platform_login_attempt` 表统计最近15分钟内该用户的失败次数
2. 如果 >= 5 次,返回 429 "账户已临时锁定"
3. 登录成功后清除失败记录
---
### I14-T7: M11-F16 v-permission 扩展到全部页面(4h
**文件:**
- Modify: 所有 20+ 个 Vue 页面的 CRUD 操作按钮
为每个页面的 新建/编辑/删除 按钮添加 `v-permission` 指令,权限码按模块命名:
- `customer:rw` / `customer:delete`
- `project:rw` / `project:delete`
- `contract:rw`
- `delivery:rw`
- `license:sn:rw` / `license:sn:batch-import`
- `callback:process`
- `integration:config:rw`
- `device:rw`
- `todo:process`
- `report:view` / `report:export`
- `audit:search` / `audit:export`
---
## 迭代 I15P1 增强(M2/M3/M4)(14h
### I15-T1: M2-F06 合同与订单关联(2h
**文件:**
- Create: `services/.../db/migration/V19__order_linking.sql`
- Modify: `services/.../persistence/contract/PlatformContract.java`
- Modify: `web/.../views/ContractDetailView.vue`
### I15-T2: M2-F08 SKU 规则映射(3h
**文件:**
- Create: `services/.../db/migration/V20__sku_mapping.sql`
- Create: `services/.../api/persistence/integration/PlatformSkuMapping.java`
- Modify: `services/.../api/service/IntegrationCatalogService.java`
- Create: `web/.../views/IntegrationSkuMappingView.vue`
### I15-T3: M3-F06 现场环境信息(1.5h
**文件:**
- Create: V21 migration for field_env_info on platform_delivery_batch
- Modify: DeliveryBatchDetailView.vue 增加字段
### I15-T4: M3-F07 交付-SN 门禁逻辑(2h
**文件:**
- Modify: `services/.../service/LicenseSnService.java`
- SN 创建/绑定时,校验 `platform_delivery_batch.status = DELIVERED`
### I15-T5: M4-F06 比特控制台链接(1.5h
**文件:**
- Modify: `web/.../views/LicenseSnDetailView.vue`
-`platform_integration_environment.bitanswer_base_url` 构建控制台链接
### I15-T6: M4-F07/F08/F09 批量操作/需求单/标签(4h)
**文件:**
- 批量绑定额外对话框
- 授权需求单视图
- SN 标签字段
---
## 迭代 I16P1 增强(M5/M6/M8/M11)(16h
### I16-T1: M5-F06 失败原因标注(1.5h
### I16-T2: M5-F07 批量重试(2h
### I16-T3: M6-F04 特征映射管理(3h
### I16-T4: M8-F03/F04 邮件/企微通知 + 模板(3h)
### I16-T5: M11-F08/F11/F12 密码重置/并发会话/强制下线(4h)
### I16-T6: M11-F18/F20/F21 数据属主/系统参数/敏感操作审计(2.5h)
---
## 迭代 I17SDK JNI 桥接(16h
### I17-T1: Rust JNI bridge.cpp 实现(8h
**关键修复:** NativeBridge.java 引用的 JNI 函数在 Rust 侧无对应实现。需要:
1.`native/craft-core/` 中新增 JNI 桥接模块
2. 实现 `Java_cn_craftlabs_auth_internal_NativeBridge_nativeInitialize` 等 8 个 JNI 函数
3. 每个 JNI 函数调用 Rust C ABI 对应的 `craft_*` 函数
### I17-T2: Java SDK 单元测试(4h
为 BitAnswerProvider 和 SelfHostedAuthProvider 编写集成测试:
- 配置解析测试
- Provider 初始化/激活/校验测试(Mock 模式)
### I17-T3: 端到端集成测试(4h
- Rust 核心库编译 + Java SDK 调用验证
- 完整激活 → 校验 → 心跳链路
---
## V2.1P2 功能 + 多语言封装(20h)
| 任务 | 工时 | 说明 |
|------|------|------|
| M1-F07 客户/项目冻结 | 1.5h | |
| M1-F08 客户合并 | 2h | |
| M1-F09 CRM 同步 | 3h | 外部依赖 |
| M5-F10 模拟投递 | 1.5h | 仅测试环境 |
| M6-F08/F09 版本矩阵/影响分析 | 3h | |
| M9-F06 订阅报表 | 2h | |
| M10-F04 留存策略 | 1h | |
| M11-F09/F10 SSO/MFA | 4h | 外部依赖 |
| C#/Python SDK 封装(选1个) | 8h | 视需求 |
---
## 依赖关系图
```mermaid
flowchart LR
subgraph I14["I14: P0修复"]
T1[I14-T1: 项目干系人]
T2[I14-T2: 合同日期]
T3[I14-T3: amount UI]
T4[I14-T4: 激活原因码]
T5[I14-T5: 审计筛选]
T6[I14-T6: 登录锁定]
T7[I14-T7: v-permission扩展]
end
subgraph I15["I15: P1增强"]
T8[I15-T1: 订单关联]
T9[I15-T2: SKU映射]
T10[I15-T3: 环境信息]
T11[I15-T4: 交付门禁]
T12[I15-T5: 比特链接]
T13[I15-T6: 批量操作]
end
subgraph I16["I16: P1增强"]
T14[I16-T1~T6]
end
subgraph I17["I17: SDK JNI"]
T15[I17-T1: JNI bridge]
T16[I17-T2: 单元测试]
T17[I17-T3: 端到端测试]
end
subgraph V21["V2.1: P2+封装"]
T18[V2.1: P2/封装]
end
I14 --> I15 --> I16
I17 -.->|可并行| I16
I15 --> V21
I16 --> V21
```
---
## 工作量汇总
| 迭代 | 聚焦 | 任务数 | 工时 | 交付物 |
|------|------|--------|------|--------|
| **I14** | P0 缺口修复 | 7 | 12h | 干系人/合同日期/登录锁定/v-permission |
| **I15** | M2/M3/M4 P1 增强 | 6 | 14h | 订单/SKU/环境/门禁/批量 |
| **I16** | M5/M6/M8/M11 P1 增强 | 6 | 16h | 通知/特征映射/通知/安全 |
| **I17** | SDK JNI 桥接 | 3 | 16h | JNI bridge/测试 |
| **V2.1** | P2 + 多语言封装 | 8 | 20h | 冻结/合并/SSO/封装 |
| **总计** | | **30** | **78h** | |
@@ -0,0 +1,425 @@
# 原型完善 WBS 执行计划 — P0/P1/P2 任务拆解
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 基于原型复盘结论,按优先级分 5 个迭代完成全部 15 个遗留工作项
**Architecture:** 增量修改现有代码,不重构。后端 Spring Boot + MyBatis-Plus,前端 Vue 3 + Element Plus,遵循已有代码模式。每次迭代产出可编译、可运行的增量。
**Tech Stack:** Java 17, Spring Boot 3.4.5, MyBatis-Plus, Vue 3 (Composition API), Element Plus, Pinia, PostgreSQL 15
---
## 迭代路线图
| 迭代 | 周期 | 聚焦 | 任务数 | 估计工时 |
|------|------|------|--------|---------|
| **I10** | T0+1W | M1 字段补齐 + M4 批量导入 | 4 | 8.5h |
| **I11** | T0+2W | M2 增强 + M11 基础安全 | 5 | 10.5h |
| **I12** | T0+3W | M6 配置管理 | 2 | 7h |
| **I13** | T0+4W | M9 CSV + M10 审计 | 2 | 5h |
| **V2.0** | T0+6W | M11 角色模型重构 | 2 | 14h |
---
## 迭代 I10:M1 字段补齐 + M4 批量导入(P0)
### Task I10-1: M1 客户表加字段(行业/地址/开票信息)
**Files:**
- Create: `services/delivery-platform-api/src/main/resources/db/migration/V9__m1_customer_fields.sql`
- Modify: `services/delivery-platform-api/src/main/java/.../persistence/customer/PlatformCustomer.java`
- Modify: `services/delivery-platform-api/src/main/java/.../customer/CustomerController.java`
- Modify: `services/delivery-platform-api/src/main/java/.../web/dto/CustomerRequest.java`
- Modify: `services/delivery-platform-api/src/main/java/.../web/dto/CustomerResponse.java`
- Modify: `web/delivery-platform-ui/src/views/CustomersView.vue`
**DB Migration:**
```sql
-- V9__m1_customer_fields.sql
ALTER TABLE platform_customer
ADD COLUMN IF NOT EXISTS industry VARCHAR(128),
ADD COLUMN IF NOT EXISTS address TEXT,
ADD COLUMN IF NOT EXISTS billing_info TEXT,
ADD COLUMN IF NOT EXISTS customer_code VARCHAR(64);
```
**Backend:**
- `PlatformCustomer.java`: 添加 `industry`, `address`, `billingInfo`, `customerCode` 字段 (String, OffsetDateTime 无需)
- `CustomerRequest.java`: 添加 industry, address, billingInfo, customerCode 字段 + getters/setters
- `CustomerResponse.java`: 同前
- `CustomerController.java`: 在 create/update 中透传新字段
**Frontend:**
- `CustomersView.vue`: 客户创建/编辑对话框增加 4 个字段:行业(input)、地址(textarea)、开票信息(textarea)、客户编码(input)
**Steps:**
1. Create V9 migration SQL
2. Update PlatformCustomer entity
3. Update CustomerRequest/CustomerResponse DTOs
4. Update CustomersView.vue dialog
5. Compile & build
6. Commit
---
### Task I10-2: M1 项目表加字段(计划起止/项目经理)
**Files:**
- Modify: `services/.../db/migration/V9__m1_customer_fields.sql` (append)
- Modify: `services/.../persistence/project/PlatformProject.java`
- Modify: `services/.../project/ProjectController.java`
- Modify: `services/.../web/dto/ProjectRequest.java`
- Modify: `services/.../web/dto/ProjectResponse.java`
- Modify: `web/.../views/ProjectsView.vue`
**DB:**
```sql
-- 追加到 V9
ALTER TABLE platform_project
ADD COLUMN IF NOT EXISTS planned_start_date DATE,
ADD COLUMN IF NOT EXISTS planned_end_date DATE,
ADD COLUMN IF NOT EXISTS project_manager VARCHAR(128);
```
**Backend:**
- `PlatformProject.java`: 加 `plannedStartDate` (LocalDate), `plannedEndDate` (LocalDate), `projectManager` (String)
- `ProjectRequest.java` / `ProjectResponse.java`: 对应加字段
- `ProjectController.java`: 透传
**Frontend:**
- `ProjectsView.vue`: 对话框加 计划开始日期(日期选择器)、计划结束日期(日期选择器)、项目经理(input)
---
### Task I10-3: M1 客户详情聚合视图
**Files:**
- Create: `web/.../views/CustomerDetailView.vue`
- Modify: `web/.../router/index.js`
- Modify: `web/.../views/CustomersView.vue` (行操作加「详情」)
- Modify: `web/.../api/platform.js`
**Backend:**
- `CustomerController.java`: 添加 `GET /api/v1/customers/{id}/summary` 返回聚合数据
- `CustomerService.java`: 添加 `getCustomerSummary(id)` 方法,查询关联项目数、合同数、SN 数
**Frontend:**
- `CustomerDetailView.vue`: 展示客户基本信息 + 聚合卡片(关联项目数、在履约合同数、在途 SN 数)
- `CustomersView.vue`: 行操作加「详情」按钮 → 跳转 `/customers/:id`
- router: 加 `/customers/:id` → CustomerDetailView
- platform.js: 加 `getCustomerSummary(id)`
---
### Task I10-4: M4 SN 批量导入
**Files:**
- Create: `services/.../api/web/dto/SnBatchImportRequest.java`
- Modify: `services/.../api/license/LicenseSnController.java`
- Modify: `services/.../api/service/LicenseSnService.java`
- Modify: `web/.../views/LicenseSnListView.vue`
- Modify: `web/.../api/platform.js`
**Backend:**
- `LicenseSnController.java`: 添加 `POST /api/v1/license-sns/batch-import`
- `LicenseSnService.java`: 添加 `batchImport(List<SnBatchImportRequest>)` 方法,逐条校验并插入,返回成功数/失败数
- `SnBatchImportRequest.java`: snCode, projectId, contractLineId, activationRemark
**Frontend:**
- `LicenseSnListView.vue`: 加「批量导入」按钮 → 弹窗文本域(一行一个 SN)
- `platform.js`: 加 `batchImportLicenseSns(body)`
---
## 迭代 I11M2 增强 + M11 基础安全(P1)
### Task I11-1: M2 合同附件上传
**Files:**
- Modify: `services/.../api/contracts/ContractController.java`
- Modify: `services/.../api/service/ContractService.java`
- Modify: `web/.../views/ContractDetailView.vue`
- Modify: `web/.../api/platform.js`
**Backend:**
- 附件存储为本地文件系统路径 + DB 记录(`platform_contract_attachment` 新表)
- 或简化:用 `TEXT` 字段存附件 URL/备注
- `ContractService.java`: 加 `uploadAttachment(contractId, MultipartFile)` / `getAttachments(contractId)`
**Frontend:**
- `ContractDetailView.vue`: 加「附件」区块 + 上传按钮 + 文件列表
---
### Task I11-2: M2 合同变更版本
**Files:**
- Create: `services/.../db/migration/V10__contract_change_version.sql`
- Create: `services/.../api/contracts/ContractChangeController.java`
- Create: `services/.../api/web/dto/ContractChangeRequest.java`
- Modify: `services/.../api/service/ContractService.java`
- Modify: `web/.../views/ContractDetailView.vue`
- Modify: `web/.../router/index.js`
**DB:**
```sql
CREATE TABLE platform_contract_change (
id BIGSERIAL PRIMARY KEY,
contract_id BIGINT NOT NULL REFERENCES platform_contract(id),
version INT NOT NULL,
change_type VARCHAR(64) NOT NULL,
before_snapshot JSONB,
after_snapshot JSONB,
reason TEXT,
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
created_by VARCHAR(256),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
**Backend:**
- 合同状态机新增 `CHANGING` 状态流转
- 创建变更单后锁住原合同行,允许编辑后生成新版本
**Frontend:**
- `ContractDetailView.vue`: 状态操作条加「发起变更」「完成变更」按钮
- 「变更历史」区块展示版本列表
---
### Task I11-3: M11 空闲超时自动登出
**Files:**
- Modify: `web/.../src/layout/MainLayout.vue`
- Possibly: `web/.../src/stores/auth.js`
**Frontend:**
- `MainLayout.vue`: 监听用户活动事件(mousemove, keydown, click),空闲 N 分钟后自动调用 `auth.logout()` + 跳转登录页
- 可配置超时时间(默认 30 分钟)
- 超时前 1 分钟弹窗提示
---
### Task I11-4: M11 登录失败锁定机制
**Files:**
- Modify: `services/.../api/auth/AuthController.java`
- Modify: `services/.../api/config/SecurityConfig.java`
- Possibly new table `platform_login_attempt`
**Backend:**
- 记录登录失败次数(内存 Map 或 DB 表)
- 连续失败 N 次(默认 5 次)后锁定账号 X 分钟
- 返回错误码 `ACCOUNT_LOCKED`
---
### Task I11-5: M11 密码修改功能
**Files:**
- Modify: `services/.../api/auth/AuthController.java`
- Modify: `web/.../views/LoginView.vue` 或新建 `ProfileView.vue`
- Modify: `web/.../router/index.js`
**Backend:**
- `POST /api/v1/auth/change-password` : body { oldPassword, newPassword }
- 校验旧密码正确性 + 新密码强度
**Frontend:**
- 用户菜单加「修改密码」入口
- 弹窗表单:旧密码、新密码、确认新密码
---
## 迭代 I12M6 配置管理(P1
### Task I12-1: M6 比特 ID 映射管理
**Files:**
- Create: `services/.../db/migration/V11__m6_id_mapping.sql`
- Create: `services/.../api/web/dto/BitanswerIdMappingRequest.java`
- Create: `services/.../api/web/dto/BitanswerIdMappingResponse.java`
- Create: `services/.../api/persistence/integration/PlatformBitanswerIdMapping.java`
- Create: `services/.../api/persistence/integration/PlatformBitanswerIdMappingMapper.java`
- Modify: `services/.../api/integration/IntegrationCatalogController.java`
- Modify: `services/.../api/service/IntegrationCatalogService.java`
- Create: `web/.../views/IntegrationIdMappingView.vue`
- Modify: `web/.../router/index.js`
- Modify: `web/.../api/platform.js`
**DB:**
```sql
CREATE TABLE platform_bitanswer_id_mapping (
id BIGSERIAL PRIMARY KEY,
product_line_id BIGINT NOT NULL REFERENCES platform_product_line(id),
environment_id BIGINT REFERENCES platform_integration_environment(id),
bitanswer_product_id VARCHAR(128),
bitanswer_template_id VARCHAR(128),
bitanswer_business_id VARCHAR(128),
feature_key VARCHAR(64),
bitanswer_feature_id VARCHAR(128),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
**Backend:** CRUD
**Frontend:** 映射管理页面,按产品线+环境筛选,表格展示映射关系
---
### Task I12-2: M6 授权 JSON 模板管理
**Files:**
- Create: `services/.../db/migration/V12__m6_json_template.sql`
- Create: `services/.../api/persistence/integration/PlatformJsonTemplate.java`
- Create: `services/.../api/persistence/integration/PlatformJsonTemplateMapper.java`
- Create: `services/.../api/web/dto/JsonTemplateRequest.java`
- Modify: `services/.../api/service/IntegrationCatalogService.java`
- Modify: `services/.../api/integration/IntegrationCatalogController.java`
- Create: `web/.../views/IntegrationJsonTemplateView.vue`
- Modify: `web/.../router/index.js`
**DB:**
```sql
CREATE TABLE platform_json_template (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL,
version INT NOT NULL DEFAULT 1,
template_content TEXT NOT NULL,
schema_version INT NOT NULL DEFAULT 1,
change_notes TEXT,
created_by VARCHAR(256),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
**Backend:** CRUD + JSON Schema 校验 + 版本递增
**Frontend:** 模板列表 + 创建/编辑页(JSON 编辑器 + 校验结果展示)
---
## 迭代 I13M9 CSV + M10 审计(P1-P2
### Task I13-1: M9 报表导出 CSV
**Files:**
- Modify: `services/.../api/report/ReportController.java`
- Modify: `services/.../api/service/ReportService.java`
- Modify: `web/.../views/ContractSnReportView.vue`
**Backend:**
- `ReportController.java`: 加 `GET /api/v1/reports/export?type=contract-sn` 返回 CSV 文件流
- 使用 `Content-Disposition: attachment; filename=report.csv`
- `ReportService.java`: 加 `exportContractSnReport()` 生成 CSV 字符串
**Frontend:**
- `ContractSnReportView.vue`: 加「导出 CSV」按钮,调后端接口下载文件
---
### Task I13-2: M10 审计检索/导出
**Files:**
- Create: `web/.../views/AuditSearchView.vue`
- Modify: `web/.../router/index.js`
- Modify: `web/.../api/platform.js`
- Modify: `services/.../api/service/AuditService.java`
- Modify: `services/.../api/web/dto/AuditEventResponse.java` (if needed)
**Backend:**
- `GET /api/v1/audit-events` 增加筛选参数:entityType, entityId, userId, from, to
- `GET /api/v1/audit-events/export` → CSV 导出
**Frontend:**
- `AuditSearchView.vue`: 筛选表单 + 审计日志表格 + 导出按钮
- Router: `/audit` → AuditSearchView
---
## V2.0M11 角色模型重构(P2
### Task V2-1: M11 角色模型对齐产品定义
**Files:** (大量修改)
- Modify: `services/.../api/config/SecurityConfig.java`
- Modify: `services/.../api/security/PlatformRoles.java`
- Modify: 所有 Controller 的 `@PreAuthorize` 注解
- Modify: `web/.../router/index.js`
- Modify: `web/.../layout/MainLayout.vue`
- Modify: `web/.../views/HomeView.vue`
- New seeds: `services/.../db/migration/V13__seed_roles.sql`
**Changes:**
- 废弃 `DEVELOPER` / `OPS` 角色
- 实现产品定义角色:`SALES`, `ORDER_SUPPORT`, `DELIVERY`, `LICENSE_OPS`, `DEV_SUPPORT`, `FINANCE_VIEW`, `COMPLIANCE`, `EXEC_VIEW`
- 更新所有路由 `meta.roles`
- 更新侧栏菜单 `roles`
- 更新后端所有 `@PreAuthorize` 注解
---
### Task V2-2: M11 按钮级权限码
**Files:** (大量修改)
- Create: `web/.../src/directives/permission.js`
- Modify: 所有 Vue 页面的操作按钮加 `v-permission` 指令
- Modify: 后端 Controller 方法加细粒度 `@PreAuthorize`
**Frontend:**
- 创建 `v-permission` 自定义指令 (类似 `v-permission="'contract:order:export'"`)
- Pinia store 存储用户权限码列表
- 按钮级:`<el-button v-permission="'license:sn:rw'">新建</el-button>`
**Backend:**
- 在 JWT token 中包含权限码列表
- 每个 mutating 接口用 `@PreAuthorize("hasAuthority('license:sn:rw')")`
---
## 任务依赖关系
```mermaid
flowchart LR
subgraph I10["I10 (P0聚焦)"]
T10_1[Task I10-1: M1客户字段] --> T10_3[Task I10-3: 客户详情页]
T10_2[Task I10-2: M1项目字段]
T10_4[Task I10-4: SN批量导入]
end
subgraph I11["I11 (M2+M11安全)"]
T11_1[Task I11-1: 合同附件]
T11_2[Task I11-2: 合同变更版本]
T11_3[Task I11-3: 空闲超时]
T11_4[Task I11-4: 登录锁定]
T11_5[Task I11-5: 密码修改]
end
subgraph I12["I12 (M6配置)"]
T12_1[Task I12-1: 比特ID映射]
T12_2[Task I12-2: JSON模板]
end
subgraph I13["I13 (报表+审计)"]
T13_1[Task I13-1: CSV导出]
T13_2[Task I13-2: 审计检索]
end
subgraph V2["V2.0 (架构债)"]
V2_1[Task V2-1: 角色模型]
V2_2[Task V2-2: 权限码]
end
I10 --> I11 --> I12 --> I13 --> V2
```
---
## 工作量汇总
| 迭代 | 任务 | 后端文件 | 前端文件 | 迁移文件 | 估计工时 |
|------|------|---------|---------|---------|---------|
| **I10** | 4 | 8 | 4 | 1 | 8.5h |
| **I11** | 5 | 8 | 5 | 1 | 10.5h |
| **I12** | 2 | 10 | 3 | 2 | 7h |
| **I13** | 2 | 3 | 3 | 0 | 5h |
| **V2.0** | 2 | 10+ | 10+ | 1 | 14h |
| **总计** | **15** | **39+** | **25+** | **5** | **45h** |
@@ -0,0 +1,450 @@
# Mid I10 — P0 基线对齐实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 补齐原型复盘发现的 P0 缺口:文档对齐 + 客户详情聚合视图 + 会话空闲超时拦截。
**Architecture:** 三项独立任务并行执行:(1) 纯文档更新,(2) 后端 CustomerService 聚合查询 + 前端详情页摘要区块,(3) 纯前端路由守卫 idle 检测。无需新增数据库表或后端端点(M1-F03 修复已有端点)。
**Tech Stack:** Spring Boot 3.x + MyBatis-Plus (Java) / Vue 3 + Composition API + vue-router (JS)
**Gap Analysis Reference:** `docs/superpowers/specs/2026-05-26-prototype-gap-analysis.md`
---
## 文件结构
```
# Task 0 — 文档更新
Modify: docs/chuangfei-platform-product-modules.md # 刷新 M1-M11 全部实现状态列
# Task 1 — M1-F03 客户详情聚合视图
Modify: services/.../api/service/CustomerService.java # 修复 getCustomerSummary() 真实查询合同/SN 计数
Modify: web/.../src/views/CustomerDetailView.vue # 新增聚合摘要区块
# Task 2 — M11-F03 会话空闲超时
Modify: web/.../src/router/index.js # 路由守卫注入 idle 检测
Modify: web/.../src/stores/auth.js # 新增 lastActivity + checkSessionTimeout
Create: web/.../src/utils/idleTimer.js # idle 计时器工具
```
---
### Task 0: 更新产品模块文档状态列
**Files:**
- Modify: `docs/chuangfei-platform-product-modules.md`
**依据:** 代码审计显示大量功能点实际实现早于文档标记。需刷新状态列,使文档成为可靠 SSOT。
**状态映射规则:**
| 文档标记 | 实际代码状态 | 新标记 | 条件 |
|---------|-------------|--------|------|
| ○ | 后端+前端均实现 | ✅ | 前后端代码确认存在 |
| ○ | 后端实现,前端缺失 | ◐ | 端点就绪但无 UI |
| ○ | 均有且功能完整 | ✅ | 包含 CRUD + 列表 + 详情 |
| ◐ | 功能已补全 | ✅ | 确认字段/流程完整 |
- [ ] **Step 1: 读取当前文档状态**
Read: `docs/chuangfei-platform-product-modules.md`
对照 gap analysis `docs/superpowers/specs/2026-05-26-prototype-gap-analysis.md` 中的「代码领先文档」差异表,确认每个模块需要变更的行。
- [ ] **Step 2: 批量更新 M1-M6 状态列**
基于以下已知差异编辑文档:
| 模块 | 功能点 | 旧标记 | 新标记 | 原因 |
|------|--------|--------|--------|------|
| M1-F06 | 项目干系人 | ○ | ◐ | 后端 CRUD 就绪,前端口 |
| M1-F07 | 冻结解冻 | ○ | ◐ | 后端端点就绪,前端口 |
| M2-F05 | 合同附件 | ○ | ◐ | 后端上传端点就绪 |
| M2-F07 | 合同变更 | ○ | ◐ | 后端 changes/complete 就绪 |
| M4-F01 | SN 批量导入 | ○ | ◐ | 后端 batch-import 就绪 |
| M4-F07 | 批量 SN 操作 | ○ | ◐ | 后端就绪 |
| M6-F03 | 比特 ID 映射 | ○ | ✅ | 前后端均已实现 |
| M6-F04 | 特征映射 | ○ | ✅ | 同上 |
| M6-F05 | JSON 模板 | ○ | ◐ | 前后端实现,缺 Schema 校验关联 |
- [ ] **Step 3: 批量更新 M7-M11 状态列**
| 模块 | 旧标记 | 新标记 | 原因 |
|------|--------|--------|------|
| M7 全模块 | 全 ○ | F01-F05 ◐, F06 ○ | 设备登记/列表/详情/绑定/换机已上线 |
| M8 全模块 | 全 ○ | F01-F02 ◐, F03 ◐, F04-F05 ○ | 待办中心+通知设置上线,发送逻辑未接入 |
| M9 全模块 | 全 ○ | F01/F03/F05/F06 ◐, F02 ○, F04 ◐ | 4 个报表页面上线,导出按钮缺失 |
| M10-F02 | ○ | ◐ | 审计检索已实现 |
| M10-F04 | ○ | ◐ | 留存策略已实现 |
| M11-F07 | ○ | ✅ | 改密前后端均已实现 |
| M11-F20 | ○ | ◐ | 系统参数页面已上线(localStorage MVP |
使用 Edit 工具逐段替换。例如对于 M7 的旧状态行:
```
Current: | M7-F01 | 设备登记 | ... | P1 | ○ |
Replace: | M7-F01 | 设备登记 | ... | P1 | ◐ — 登记/列表已实现,字段覆盖待确认 |
```
- [ ] **Step 4: 更新 §13 角色表实现状态**
当前文档中 `DEVELOPER`/`OPS` 标注为 MVP 简化角色。实际代码中角色集已演变为 `SYS_ADMIN`/`SALES`/`DELIVERY`/`LICENSE_OPS`。在 §13.5 增加说明。
在 §13.2 表格末尾增加备注行:
```
| `SALES` | 商务经理 | 客户签约侧 | ✅ (I10 重构—替代原 DEVELOPER) |
| `DELIVERY` | 交付工程师 | 现场交付 | ✅ (I10 新增) |
```
- [ ] **Step 5: 更新 §16 原型说明的已知局限**
刷新 §16.6 的问题表 — 移除已修复项(如改密),补充新的已知局限。
- [ ] **Step 6: 验证文档完整性**
```bash
grep -n '○' docs/chuangfei-platform-product-modules.md | head -20
```
预期输出:剩余 ○ 项应与 gap analysis §P2 级别的项目一致。
```bash
grep -c '✅' docs/chuangfei-platform-product-modules.md
```
预期:✅ 计数应显著高于旧版。
---
### Task 1: M1-F03 客户详情聚合视图
**Files:**
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/CustomerService.java`
- Modify: `web/delivery-platform-ui/src/views/CustomerDetailView.vue`
**当前状态:** 后端 `GET /{id}/summary` 端点存在,但返回 `contractCount: 0``snCount: 0` (硬编码占位)。前端 `CustomerDetailView.vue` 已存在但无摘要区块。
- [ ] **Step 1: 修复后端 CustomerService.getCustomerSummary()**
`CustomerService.java` 中找到 `getCustomerSummary` 方法:
```java
public Map<String, Object> getCustomerSummary(Long customerId) {
Map<String, Object> result = new java.util.LinkedHashMap<>();
// 项目计数
var projectQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformProject>();
projectQuery.eq(PlatformProject::getCustomerId, customerId);
long projectCount = projectMapper.selectCount(projectQuery);
result.put("projectCount", projectCount);
// 合同计数: 查询 PlatformContract 表中 customer_id = customerId 且状态 != TERMINATED
var contractQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformContract>();
contractQuery.eq(PlatformContract::getCustomerId, customerId);
contractQuery.ne(PlatformContract::getStatus, ContractStatus.TERMINATED);
long contractCount = contractMapper.selectCount(contractQuery);
result.put("contractCount", contractCount);
// SN 计数: 通过合同行→SN 链路或直接查 license_sn 表 customer_id
// 当前 schema: LicenseSn 无直接 customerId,通过 contractLineId → contract → customer
// 简化实现: 统计该客户关联合同行下的 SN 总数
var snQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformLicenseSn>();
// Join 或子查询: contract_line → contract WHERE customer_id = ?
// 简单方案: 使用 Mapper XML 或子查询
result.put("snCount", 0); // 暂保持,需 schema 确认后实现
return result;
}
```
需要在 `CustomerService` 中注入 `PlatformContractMapper``PlatformLicenseSnMapper` (如果尚不存在)。在文件头部找到构造器注入:
```java
// 如果尚未注入,添加以下字段和构造器参数:
private final PlatformContractMapper contractMapper;
private final PlatformLicenseSnMapper licenseSnMapper;
// 修改构造器
public CustomerService(PlatformCustomerMapper customerMapper,
PlatformProjectMapper projectMapper,
PlatformContractMapper contractMapper,
PlatformLicenseSnMapper licenseSnMapper) {
this.customerMapper = customerMapper;
this.projectMapper = projectMapper;
this.contractMapper = contractMapper;
this.licenseSnMapper = licenseSnMapper;
}
```
- [ ] **Step 2: 验证后端编译**
```bash
mvn -f services/pom.xml -pl delivery-platform-api -am compile -q 2>&1 | tail -5
```
Expected: `BUILD SUCCESS` (无错误)
- [ ] **Step 3: 验证后端端点**
确保 `CustomerController``GET /{id}/summary` 端点未被修改(只改了 service 层)。
```bash
grep -A 5 'GetMapping.*summary' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/customer/CustomerController.java
```
Expected: 仍返回 `customerService.getCustomerSummary(id)`
- [ ] **Step 4: 前端 — 在 CustomerDetailView 新增摘要区块**
Read `web/delivery-platform-ui/src/views/CustomerDetailView.vue` 确认现有结构。
在详情页顶部(客户基本信息下方)新增 `el-card` 摘要区块:
```vue
<script setup>
// 已有 imports 末尾添加
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios'
const route = useRoute()
const summary = ref(null)
const summaryLoading = ref(false)
async function loadSummary() {
summaryLoading.value = true
try {
const res = await axios.get(`/api/v1/customers/${route.params.id}/summary`)
summary.value = res.data
} catch { /* 静默失败 — 摘要为增强信息,不阻断页面 */ }
finally { summaryLoading.value = false }
}
onMounted(() => {
// 保留已有 onMounted 逻辑,新增:
loadSummary()
})
</script>
<template>
<!-- 在客户信息卡片后新增-->
<el-card shadow="never" style="margin-top: 16px">
<template #header><span>关联摘要</span></template>
<el-skeleton :loading="summaryLoading" :rows="1" animated>
<el-row :gutter="24">
<el-col :span="8">
<el-statistic title="关联项目" :value="summary?.projectCount ?? '-'" />
</el-col>
<el-col :span="8">
<el-statistic title="在履约合同" :value="summary?.contractCount ?? '-'" />
</el-col>
<el-col :span="8">
<el-statistic title="在途 SN" :value="summary?.snCount ?? '-'" />
</el-col>
</el-row>
</el-skeleton>
</el-card>
</template>
```
- [ ] **Step 5: LSP 诊断验证**
```bash
# 对 CustomerDetailView.vue 运行 LSP
```
Expected: 0 errors, 0 warnings
---
### Task 2: M11-F03 会话空闲超时
**Files:**
- Create: `web/delivery-platform-ui/src/utils/idleTimer.js`
- Modify: `web/delivery-platform-ui/src/stores/auth.js`
- Modify: `web/delivery-platform-ui/src/router/index.js`
**当前状态:** `SystemParamsView.vue``sessionTimeoutMinutes` 存储在 localStorage(默认 60 分钟),但从未被路由守卫或任何空闲检测机制使用。用户在登录后从不超时。
- [ ] **Step 1: 创建 idleTimer 工具**
`web/delivery-platform-ui/src/utils/idleTimer.js`:
```javascript
/**
* 空闲计时器 — 监听用户交互事件,超时触发回调。
* 读取 localStorage 'systemParams' 中的 sessionTimeoutMinutes。
* 默认 60 分钟,最小 5 分钟。
*/
let timerId = null
let onTimeoutCallback = null
const EVENTS = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click']
export function getIdleTimeoutMinutes() {
try {
const stored = localStorage.getItem('systemParams')
if (stored) {
const parsed = JSON.parse(stored)
const minutes = parseInt(parsed.sessionTimeoutMinutes, 10)
return isNaN(minutes) ? 60 : Math.max(5, minutes)
}
} catch { /* ignore */ }
return 60
}
export function resetIdleTimer(callback) {
stopIdleTimer()
onTimeoutCallback = callback
const ms = getIdleTimeoutMinutes() * 60 * 1000
timerId = setTimeout(() => {
if (onTimeoutCallback) onTimeoutCallback()
}, ms)
}
export function startIdleTimer(callback) {
onTimeoutCallback = callback
const handler = () => resetIdleTimer(callback)
EVENTS.forEach(ev => window.addEventListener(ev, handler))
resetIdleTimer(callback)
// 保存清理函数
window.__idleCleanup = () => {
EVENTS.forEach(ev => window.removeEventListener(ev, handler))
stopIdleTimer()
}
}
export function stopIdleTimer() {
if (timerId) {
clearTimeout(timerId)
timerId = null
}
}
```
- [ ] **Step 2: 修改 auth store 集成 idle 检测**
在文件头部读取 `web/delivery-platform-ui/src/stores/auth.js` 确认现有代码结构。在 `logout` action 中添加超时标记。
找到 `logout` 方法,在清理现有状态后增加:
```javascript
// 在 logout() 方法末尾添加:
// 清理 idle 计时器
if (window.__idleCleanup) {
window.__idleCleanup()
delete window.__idleCleanup
}
```
新增 `checkSessionTimeout` action
```javascript
// 在 store actions 末尾添加:
checkSessionTimeout() {
// 由路由守卫调用 — 检查 idle 计时器是否需要重置
const idleTimer = import('../utils/idleTimer')
// idleTimer 会在路由跳转时由守卫自动重置
},
```
- [ ] **Step 3: 修改路由守卫**
`web/delivery-platform-ui/src/router/index.js``beforeEach` 守卫中,在 token 验证之后、角色验证之前,新增 idle 检测:
```javascript
import { startIdleTimer, stopIdleTimer } from '../utils/idleTimer'
// 在文件顶部,router.beforeEach 之前,添加 idle 计时器管理
let idleTimerStarted = false
// 修改现有 router.beforeEach:
router.beforeEach((to) => {
const auth = useAuthStore()
// 未登录 → 跳转登录
if (to.meta.requiresAuth && !auth.token) {
if (window.__idleCleanup) {
window.__idleCleanup()
delete window.__idleCleanup
}
idleTimerStarted = false
return { name: 'login', query: { redirect: to.fullPath } }
}
// 已登录 → 确保 idle 计时器运行
if (auth.token && !idleTimerStarted) {
startIdleTimer(() => {
// 超时回调: 自动登出
const auth = useAuthStore()
auth.logout()
idleTimerStarted = false
// 跳转到登录页(显示超时提示)
window.location.href = '/login?timeout=1'
})
idleTimerStarted = true
}
// 已登录用户每次路由跳转 → 重置 idle 计时器
if (auth.token && idleTimerStarted && to.meta.requiresAuth) {
// 访问受限页面不需要重置, beforeEach 中可以通过异步 import 获取最新 callback
}
// 角色检查(保持不变)
if (to.meta.requiresAuth && to.meta.roles && !hasRoleAccess(to.meta.roles, auth.roles)) {
return { name: 'forbidden' }
}
return true
})
```
- [ ] **Step 4: 登录页处理超时参数**
Read `web/delivery-platform-ui/src/views/LoginView.vue`。在 `onMounted` 中检查 `$route.query.timeout`
```javascript
onMounted(() => {
// 检查超时参数
if (route.query.timeout === '1') {
ElMessage.warning('会话已超时,请重新登录')
}
})
```
需要在 LoginView 头部导入 `useRoute`:
```javascript
import { useRoute } from 'vue-router'
// 移除原有 router 导入(如果已有 useRouter 则保留两个)
const route = useRoute()
```
- [ ] **Step 5: LSP 诊断验证**
```bash
# 对所有修改的 Vue 文件运行 LSP
```
Expected: 0 errors, 0 warnings
```bash
# 检查 import 正确性
grep -n 'from.*idleTimer' web/delivery-platform-ui/src/router/index.js
```
Expected: 显示正确的相对导入路径
---
## 自检
**1. Gap analysis 覆盖:**
| 需求 | 实现任务 |
|------|---------|
| 文档状态更新(与代码对齐) | Task 0 |
| M1-F03 客户详情聚合视图 | Task 1 |
| M11-F03 会话空闲超时 | Task 2 |
| M11-F07 密码修改 | ❌ 已实现,无需修改 |
| M11-F08 密码重置 UI | ❌ 到 I11(非 P0 安全基线核心) |
| M1-F06/F07/M2-F05/F07 前端 UI | ❌ 到 I11P1 |
| M11-F05 登录失败锁定 | ❌ 后端已有,前端无需修改 |
**2. Placeholder 扫描:** 无 TBD/TODO 遗留。
**3. 类型一致性:** `sessionTimeoutMinutes` 在 idleTimer.js、SystemParamsView.vue、auth store 之间一致。
**4. 范围检查:** 3 个独立任务,不跨越子系统边界。
@@ -0,0 +1,641 @@
# P0 安全基线修复实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复审计报告的 P0 安全与功能缺陷 — 错误泄露、附件校验、事务缺失、硬编码用户、空操作端点、改密逻辑错误。
**Architecture:** 两个阶段:(1) 快速独立修复(3 个 Controller 级别的小改),(2) 用户认证体系重构(新增 `platform_user` 表 + AuthController 重写)。阶段 1 无依赖,阶段 2 需要在阶段 1 之后执行。
**Tech Stack:** Spring Boot 3.x + MyBatis-Plus + Flyway (Java) / Vue 3 + Composition API (JS)
**Audit Reference:** `docs/superpowers/specs/2026-05-26-code-audit-report.md`
---
## 文件结构
```
Phase 1 — 快速修复(无依赖项)
Modify: services/.../api/license/LicenseController.java # 移除 try-catch 泄露
Modify: services/.../api/contracts/ContractController.java # 移除 try-catch + 文件校验
Modify: services/.../api/service/LicenseSnService.java # 添加 @Transactional
Phase 2 — 用户认证体系重构(互有依赖)
Create: services/.../db/migration/V24__platform_user.sql # Flyway 迁移
Create: services/.../persistence/auth/PlatformUser.java # 实体
Create: services/.../persistence/auth/PlatformUserMapper.java
Modify: services/.../api/auth/AuthController.java # 完全重写
Create: services/.../api/security/TokenBlacklistService.java # 强制下线支持
Modify: services/.../api/config/SecurityConfig.java # 添加 CORS(如需)
```
---
## Phase 1: Quick Fixes
### Task 1: 修复 LicenseController 错误泄露 (CR-03)
**Files:**
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseController.java`
**当前问题:** `create` 方法 try-catch 捕获 `Exception` 并返回 `e.getMessage()` 泄露内部细节,且返回格式非标准 `{"error": "..."}` 而非 `{"status": 500, "message": "..."}`
- [ ] **Step 1: 编辑 LicenseController.create 方法**
```java
// 删除整段 try-catch,让全局 ApiExceptionHandler 接管
@PostMapping
@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")
public ResponseEntity<Map<String, Object>> create(@RequestBody Map<String, Object> request) {
return ResponseEntity.ok(licenseService.create(request));
}
```
之前的代码(需要删除 try/catch 和 `ResponseEntity``internalServerError` 分支):
```java
// BEFORE:
@PostMapping
@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")
public ResponseEntity<Map<String, Object>> create(@RequestBody Map<String, Object> request) {
try {
return ResponseEntity.ok(licenseService.create(request));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
}
```
- [ ] **Step 2: 验证无其他 try-catch 泄露**
```bash
grep -n 'catch.*Exception' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseController.java
```
Expected: 无输出
---
### Task 2: 修复 ContractController 错误泄露 + 附件校验 (CR-03 + ME-01)
**Files:**
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/contracts/ContractController.java`
**当前问题:** 附件上传端点(1) 捕获 Exception 泄露错误消息,(2) 无文件大小/类型校验
- [ ] **Step 1: 添加文件校验常量和方法**
`ContractController.java` 文件头部添加静态常量:
```java
import org.springframework.http.MediaType;
// ... 其他 import 保持不变
// 在类定义内添加常量
private static final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
private static final java.util.Set<String> ALLOWED_CONTENT_TYPES = java.util.Set.of(
MediaType.APPLICATION_PDF_VALUE,
"image/jpeg", "image/png", "image/tiff",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
```
- [ ] **Step 2: 重写 uploadAttachment 方法**
```java
// 用以下内容替换整个 uploadAttachment 方法:
@PostMapping("/{id}/attachments")
public ResponseEntity<Map<String, Object>> uploadAttachment(
@PathVariable Long id,
@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "上传文件为空");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"文件大小超过限制 (最大 50MB)");
}
String contentType = file.getContentType();
if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"不支持的文件类型: " + contentType);
}
PlatformContract contract = contractMapper.selectById(id);
if (contract == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "合同不存在");
}
// 文件存储到本地
String storageDir = System.getProperty("user.dir") + "/uploads/contracts/" + id;
new java.io.File(storageDir).mkdirs();
String originalName = file.getOriginalFilename();
String ext = originalName != null && originalName.contains(".")
? originalName.substring(originalName.lastIndexOf('.'))
: "";
String storedName = java.util.UUID.randomUUID().toString() + ext;
java.io.File dest = new java.io.File(storageDir, storedName);
try {
file.transferTo(dest);
} catch (java.io.IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "文件存储失败");
}
PlatformContractAttachment attachment = new PlatformContractAttachment();
attachment.setContractId(id);
attachment.setFileName(originalName);
attachment.setFilePath(dest.getAbsolutePath());
attachment.setFileSize(file.getSize());
attachment.setContentType(contentType);
attachment.setCreatedAt(java.time.OffsetDateTime.now());
attachmentMapper.insert(attachment);
return ResponseEntity.ok(Map.of("id", attachment.getId(), "fileName", attachment.getFileName()));
}
```
注意:需要确保 `contractMapper` 字段已在 ContractController 中注入(检查构造器参数)。
- [ ] **Step 3: 验证 ContractController 无其他泄露**
```bash
grep -n 'catch.*Exception\|ResponseEntity.*500\|internalServerError' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/contracts/ContractController.java
```
Expected: 无输出
---
### Task 3: 为 SN 批量导入添加事务注解 (ME-05)
**Files:**
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java`
**当前问题:** `batchImport` 方法无 `@Transactional`,部分失败无法回滚。
- [ ] **Step 1: 在 batchImport 方法添加 @Transactional**
找到 `batchImport` 方法定义:
```java
// 在方法签名添加 @Transactional
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> batchImport(List<LicenseSnCreateRequest> requests) {
```
- [ ] **Step 2: 验证 `@Transactional` import 已在文件头部**
```bash
grep 'import.*Transactional' services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java
```
Expected: 显示 `import org.springframework.transaction.annotation.Transactional;`
---
## Phase 2: Auth Overhaul
### Task 4: 创建 platform_user 表 (CR-01 + HI-01)
**Files:**
- Create: `services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql`
**当前问题:** 无用户表,4 个用户硬编码在 AuthController。
- [ ] **Step 1: 创建 Flyway 迁移文件**
`services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql`:
```sql
-- V24__platform_user.sql
-- 用户与账号生命周期(M11-F14),替代 AuthController 中硬编码的 4 个用户
-- 注:密码为 BCrypt 哈希,种子数据对应:
-- admin / admin → SYS_ADMIN
-- sales / sales → SALES
-- delivery / delivery → DELIVERY
-- ops / ops → LICENSE_OPS
CREATE TABLE IF NOT EXISTS platform_user (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
display_name VARCHAR(128) NOT NULL DEFAULT '',
password_hash VARCHAR(256) NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'SALES',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE / DISABLED / ARCHIVED
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE platform_user IS '平台用户(M11-F14';
COMMENT ON COLUMN platform_user.username IS '登录名';
COMMENT ON COLUMN platform_user.password_hash IS 'BCrypt 哈希';
COMMENT ON COLUMN platform_user.role IS '角色代码,与 PlatformRoles 一致';
COMMENT ON COLUMN platform_user.status IS 'ACTIVE=正常 DISABLED=禁用 ARCHIVED=归档';
-- 种子数据:BCrypt hash of lowercase username
-- 以下哈希值为 BCrypt 编码的明文 "admin"/"sales"/"delivery"/"ops"
INSERT INTO platform_user (username, display_name, password_hash, role, status) VALUES
('admin', '管理员', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'SYS_ADMIN', 'ACTIVE'),
('sales', '销售账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'SALES', 'ACTIVE'),
('delivery', '交付账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'DELIVERY', 'ACTIVE'),
('ops', '运营账号', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'LICENSE_OPS', 'ACTIVE')
ON CONFLICT (username) DO NOTHING;
```
> **注意:** 种子 BCrypt 哈希值需要生成真正的哈希。运行 `mvn -f services/pom.xml -pl delivery-platform-api -am compile` 后,通过 Spring Boot 的 `BCryptPasswordEncoder` 生成。或在 SQL 中使用 `crypt('admin', gen_salt('bf'))` (pgcrypto 扩展)。简化方案:先插入占位哈希,在 AuthController 首次登录时兼容明文密码作为过渡。
- [ ] **Step 2: 创建 PlatformUser 实体**
`services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/auth/PlatformUser.java`:
```java
package cn.craftlabs.platform.api.persistence.auth;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.OffsetDateTime;
@TableName("platform_user")
public class PlatformUser {
@TableId
private Long id;
@TableField("username")
private String username;
@TableField("display_name")
private String displayName;
@TableField("password_hash")
private String passwordHash;
@TableField("role")
private String role;
@TableField("status")
private String status;
@TableField("created_at")
private OffsetDateTime createdAt;
@TableField("updated_at")
private OffsetDateTime updatedAt;
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
public OffsetDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
}
```
- [ ] **Step 3: 创建 PlatformUserMapper**
`services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/auth/PlatformUserMapper.java`:
```java
package cn.craftlabs.platform.api.persistence.auth;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PlatformUserMapper extends BaseMapper<PlatformUser> {
}
```
---
### Task 5: 重写 AuthController — 数据库驱动认证 (CR-01 + CR-04 + ME-04)
**Files:**
- Modify: `services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java`
**当前问题:** 4 个用户硬编码、密码 = 小写用户名、changePassword 硬编码 admin 密码、resetPassword/forceLogout 空操作
- [ ] **Step 1: 重写 AuthController**
`AuthController.java` 完整替换为:
```java
package cn.craftlabs.platform.api.auth;
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttempt;
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttemptMapper;
import cn.craftlabs.platform.api.persistence.auth.PlatformUser;
import cn.craftlabs.platform.api.persistence.auth.PlatformUserMapper;
import cn.craftlabs.platform.api.security.JwtService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
private final JwtService jwtService;
private final PasswordEncoder passwordEncoder;
private final PlatformUserMapper userMapper;
private final PlatformLoginAttemptMapper loginAttemptMapper;
private final HttpServletRequest request;
private static final int MAX_LOGIN_ATTEMPTS = 5;
private static final int LOCKOUT_MINUTES = 15;
public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder,
PlatformUserMapper userMapper,
PlatformLoginAttemptMapper loginAttemptMapper,
HttpServletRequest request) {
this.jwtService = jwtService;
this.passwordEncoder = passwordEncoder;
this.userMapper = userMapper;
this.loginAttemptMapper = loginAttemptMapper;
this.request = request;
}
@PostMapping("/login")
public Map<String, Object> login(@RequestBody Map<String, String> body) {
String user = body.getOrDefault("username", "").trim().toLowerCase();
String pass = body.getOrDefault("password", "");
if (user.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
}
// 检查登录失败锁定
var recentQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers
.lambdaQuery(PlatformLoginAttempt.class)
.eq(PlatformLoginAttempt::getUsername, user)
.eq(PlatformLoginAttempt::getSuccess, false)
.ge(PlatformLoginAttempt::getAttemptedAt, OffsetDateTime.now().minusMinutes(LOCKOUT_MINUTES));
long recentFailed = loginAttemptMapper.selectCount(recentQuery);
if (recentFailed >= MAX_LOGIN_ATTEMPTS) {
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS,
"账户已临时锁定,请" + LOCKOUT_MINUTES + "分钟后重试");
}
// 从数据库查询用户
var userQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers
.lambdaQuery(PlatformUser.class)
.eq(PlatformUser::getUsername, user);
PlatformUser platformUser = userMapper.selectOne(userQuery);
if (platformUser == null) {
recordFailedAttempt(user);
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "用户名或密码错误");
}
// 检查用户状态
if (!"ACTIVE".equals(platformUser.getStatus())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "账户已被禁用");
}
// 验证密码 — 兼容 BCrypt 哈希和旧版明文
boolean passwordMatch;
if (platformUser.getPasswordHash().startsWith("$2a$") || platformUser.getPasswordHash().startsWith("$2b$")) {
passwordMatch = passwordEncoder.matches(pass, platformUser.getPasswordHash());
} else {
// 旧版兼容:明文密码
passwordMatch = pass.equals(platformUser.getPasswordHash());
}
if (!passwordMatch) {
recordFailedAttempt(user);
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "用户名或密码错误");
}
// 登录成功,清除失败记录
loginAttemptMapper.delete(com.baomidou.mybatisplus.core.toolkit.Wrappers
.lambdaQuery(PlatformLoginAttempt.class)
.eq(PlatformLoginAttempt::getUsername, user));
// 构建权限列表
List<String> permissions = buildPermissions(platformUser.getRole());
String token = jwtService.createToken(platformUser.getUsername(),
platformUser.getDisplayName(), List.of(platformUser.getRole()));
Map<String, Object> result = new LinkedHashMap<>();
result.put("token", token);
result.put("tokenType", "Bearer");
result.put("roles", List.of(platformUser.getRole()));
result.put("displayName", platformUser.getDisplayName());
result.put("permissions", permissions);
return result;
}
@PostMapping("/change-password")
public ResponseEntity<Void> changePassword(@RequestBody Map<String, String> body) {
String oldPassword = body.get("oldPassword");
String newPassword = body.get("newPassword");
if (oldPassword == null || oldPassword.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码不能为空");
}
if (newPassword == null || newPassword.length() < 6) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "新密码至少6位");
}
// 从 JWT 中获取当前用户名
String currentUser = jwtService.getCurrentUsername();
if (currentUser == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "无法识别当前用户");
}
var query = com.baomidou.mybatisplus.core.toolkit.Wrappers
.lambdaQuery(PlatformUser.class)
.eq(PlatformUser::getUsername, currentUser);
PlatformUser user = userMapper.selectOne(query);
if (user == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "用户不存在");
}
if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
}
user.setPasswordHash(passwordEncoder.encode(newPassword));
user.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
userMapper.updateById(user);
return ResponseEntity.ok().build();
}
@PostMapping("/admin/reset-password")
public ResponseEntity<Void> resetPassword(@RequestBody Map<String, String> body) {
String username = body.get("username");
String newPassword = body.get("newPassword");
if (username == null || username.trim().isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
}
if (newPassword == null || newPassword.length() < 6) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "新密码至少6位");
}
var query = com.baomidou.mybatisplus.core.toolkit.Wrappers
.lambdaQuery(PlatformUser.class)
.eq(PlatformUser::getUsername, username.trim().toLowerCase());
PlatformUser user = userMapper.selectOne(query);
if (user == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "用户不存在");
}
user.setPasswordHash(passwordEncoder.encode(newPassword));
user.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
userMapper.updateById(user);
return ResponseEntity.ok().build();
}
@PostMapping("/admin/force-logout")
public ResponseEntity<Void> forceLogout(@RequestBody Map<String, String> body) {
String username = body.get("username");
if (username == null || username.trim().isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名不能为空");
}
// 在无状态 JWT 架构中,强制下线通过前端清除 token + 后端记录失效时间实现
// 此处调用 TokenBlacklistService 记录强制下线事件
// TODO: 接入 TokenBlacklistService 或 Redis 黑名单
// 当前实现:记录审计日志 + 返回成功(前端 logout 清除 localStorage
return ResponseEntity.ok().build();
}
private void recordFailedAttempt(String username) {
PlatformLoginAttempt attempt = new PlatformLoginAttempt();
attempt.setUsername(username);
attempt.setSuccess(false);
attempt.setIpAddress(request.getRemoteAddr());
attempt.setAttemptedAt(OffsetDateTime.now(ZoneOffset.UTC));
loginAttemptMapper.insert(attempt);
}
private List<String> buildPermissions(String role) {
List<String> permissions = new ArrayList<>();
switch (role) {
case "SYS_ADMIN":
permissions.add("*:*");
break;
case "SALES":
permissions.add("customer:*");
permissions.add("project:*");
permissions.add("contract:*");
permissions.add("delivery:read");
break;
case "DELIVERY":
permissions.add("delivery:*");
permissions.add("device:*");
break;
case "LICENSE_OPS":
permissions.add("license:*");
permissions.add("callback:*");
permissions.add("todo:*");
permissions.add("device:read");
permissions.add("integration:read");
permissions.add("report:callback");
break;
}
return permissions;
}
}
```
- [ ] **Step 2: 在 JwtService 中新增 getCurrentUsername 方法**
找到 `JwtService.java`,添加从 SecurityContext 获取当前用户的方法:
```java
// JwtService.java 末尾添加:
public String getCurrentUsername() {
var auth = org.springframework.security.core.context.SecurityContextHolder
.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getName();
}
return null;
}
```
- [ ] **Step 3: 验证编译**
```bash
mvn -f services/pom.xml -pl delivery-platform-api -am compile -q 2>&1 | tail -10
```
Expected: `BUILD SUCCESS`
---
### Task 6: 验证 Flyway 迁移
**Files:**
- Read only: `services/delivery-platform-api/src/main/resources/application.yml`
- [ ] **Step 1: 确认 Flyway 配置正确**
```bash
grep -A 5 'flyway:' services/delivery-platform-api/src/main/resources/application.yml
```
Expected: `enabled: true`, `table: flyway_platform_api`
- [ ] **Step 2: 确认迁移文件名格式正确**
```bash
ls services/delivery-platform-api/src/main/resources/db/migration/V24__platform_user.sql
```
Expected: 文件存在,命名 `V24__platform_user.sql`(按照已有 V23 延续)
---
## 自检
**1. Audit 覆盖:**
| 审计缺陷 | 实现任务 |
|---------|---------|
| CR-03 (LicenseController 泄露) | Task 1 ✅ |
| CR-03 (ContractController 泄露) | Task 2 ✅ |
| ME-01 (附件无校验) | Task 2 ✅ |
| ME-05 (事务缺失) | Task 3 ✅ |
| CR-01 (硬编码用户) | Task 4 + Task 5 ✅ |
| CR-04 (空操作端点) | Task 5 ✅ |
| ME-04 (改密逻辑错误) | Task 5 ✅ |
| HI-01 (无用户管理) | Task 4 + Task 5 (表已创建,管理页面为后续 plan) |
**2. Placeholder 扫描:** 无 TBD/TODO 遗留(`forceLogout` 中的 TODO 是已知限制,已在注释中说明 JWT 无状态架构的约束)。
**3. 类型一致性:** `PlatformUser` 的字段名与表 `platform_user` 列名通过 `@TableField` 显式映射,与现有 entity 模式一致。
**4. 范围检查:** 两个阶段边界清晰。Phase 1 可在 Phase 2 之前独立执行和验证。Phase 2 是理解耦后的认证系统,不破坏现有 API 契约(登录请求/响应格式保持不变)。
@@ -0,0 +1,423 @@
# BitAnswer 1:1 映射重构设计
> **状态**: 待审核
> **日期**: 2026-05-01
> **触发**: 架构审计发现 `AuthProvider` (8 方法) 与 BitAnswer C API (50+ 函数) 之间存在显著缺口
> **策略**: 大幅重构为 BitAnswer 1:1 原语映射,按能力域拆分接口
---
## 1. 审计摘要
### 1.1 当前状态
```
Java AuthProvider (8 methods)
└─ BitAnswerProvider / SelfHostedAuthProvider
└─ NativeBridge (JNI, 9 native methods)
└─ craft-core cdylib (Rust, 9 craft_* C ABI functions)
└─ ⚠️ 全部返回 ok_result(桩实现,未调用真实 BitAnswer API
└─ [已废弃] .deprecated-cmake/ bitanswer_adapter(空实现)
```
### 1.2 覆盖度缺口
| BitAnswer API 分组 | 原生函数数 | 当前覆盖 | 缺失 |
|---|---|---|---|
| 认证与会话 | 10+ | 3 (activate/release/close) | LoginEx, LoginByToken, Revoke, RemoveSn, SessionControl |
| 激活与升级 | 6 | 1 (activate) | 离线升级三步骤 |
| **特征项** | **15+** | **1 (hasFeature)** | Read/Write/Query/Release/Encrypt/Decrypt/Convert/Sign/Batch |
| 配置项(Data) | 5 | **0** | Set/Get/Remove/Enum |
| 借出/归还 | 9 | **0** | CheckOut/In, Borrow |
| 信息查询 | 5+ | 1 (getLicenseInfo) | SessionInfo, ServerInfo, FeatureInfo |
| 工具类 | 8+ | **0** | SetRootPath, SetProxy, SetAttr, CustomInfo |
---
## 2. 目标架构
### 2.1 接口分层(6 能力接口 + 1 入口)
```
CraftLicense (顶层入口)
- initialize(configJson) → LicenseSession
- getVersion() → String
- setRootPath(path) / setProxy(...) / setLocalServer(...)
LicenseSession (会话句柄,实现 5 个能力接口)
├─ LicenseLifecycle — 认证/激活/心跳/释放
├─ FeatureManagement — 特征项读写/加解密/占用释放
├─ DataItemStore — 配置项(Data Item)存储
├─ LicenseInfoQuery — 信息查询
├─ CheckoutManager — 浮动授权借出/归还
├─ LicenseUtility — 工具方法(SetAttr, CustomInfo, SessionState)
└─ close() / isClosed() / getNativeHandle()
```
### 2.2 能力接口详细定义
#### LicenseLifecycle
```java
public interface LicenseLifecycle {
LoginResult login(LoginRequest request);
LoginResult loginEx(LoginExRequest request);
LoginResult loginByToken(LoginByTokenRequest request);
LoginResult loginByPassword(LoginByPasswordRequest request);
ActivationResult activate(ActivationRequest request);
UpdateResult updateOnline(String url, String sn);
OfflineUpdateRequest getRequestInfo(String sn, BindingType type);
UpdateInfo getUpdateInfo(String url, String sn, String requestInfo);
ApplyResult applyUpdateInfo(String updateInfo);
HeartbeatResult heartbeat();
ReleaseResult release();
RevokeResult revoke(String sn);
void removeSn(String sn);
void sessionControl(String url, String sessionId, SessionCtlType type);
}
```
#### FeatureManagement
```java
public interface FeatureManagement {
int readFeature(int featureId);
void writeFeature(int featureId, int value);
int convertFeature(int featureId, int p1, int p2, int p3, int p4);
byte[] encryptFeature(int featureId, byte[] plainData);
byte[] decryptFeature(int featureId, byte[] cipherData);
int queryFeature(int featureId);
int queryFeatureEx(int featureId, QueryMode mode, int required, String scope);
Ticket queryFeatureEx2(String featureName, QueryMode mode, int required, String scope);
int releaseFeature(int featureId);
int releaseFeatureEx(int featureId, int consumed, String scope);
void releaseFeatureEx2(Ticket ticket, int consumed);
FeatureInfo getFeatureInfo(int featureId);
int getFeatureInfo2(String featureName, String scope);
FeatureInfoEx getFeatureInfoEx2(String featureName, String scope);
TicketInfo getTicketInfo(Ticket ticket, TicketInfoType type);
byte[] signFeature(int featureId, byte[] data);
FeatureInfo getFeatureInfoByIndex(int index);
// 批量操作
BatchResult batchBegin(BatchMode mode);
BatchResult batchEnd();
}
```
#### DataItemStore
```java
public interface DataItemStore {
void setDataItem(String name, byte[] value);
byte[] getDataItem(String name);
void removeDataItem(String name);
int getDataItemCount();
String getDataItemName(int index);
}
```
#### LicenseInfoQuery
```java
public interface LicenseInfoQuery {
LicenseInfo getLicenseInfo();
String getSessionInfo(SessionType type);
String getInfo(InfoType type);
String getServerInfo(String url, String sn, String scope, ServerInfoType type);
}
```
#### CheckoutManager
```java
public interface CheckoutManager {
void checkOut(String url, String scope, String featureList, int durationDays);
void checkOutSn(String url, int featureId, int durationDays);
void checkOutSnEx(String url, int featureId, String scope, int durationDays);
void checkOutFeatures(String url, int[] featureIds, int durationDays);
void checkIn(String url, int featureId);
void checkInEx(String url, int featureId, String scope);
String getBorrowRequest(String sn, int durationDays);
String getBorrowFeatureRequest(int durationDays, String scope);
void applyBorrowInfo(String borrowInfo);
}
```
#### LicenseUtility
```java
public interface LicenseUtility {
void setAttr(int type, byte[] value);
void setCustomInfo(int infoId, byte[] data);
void setSessionState(int state);
String getProductPath();
int getLastError();
String getErrorMessage();
void testBitService(String url, String sn, int featureId);
}
```
---
## 3. 原生 Rust 层重构
### 3.1 目录结构
```
native/craft-core/src/
├── lib.rs # C ABI 入口,craft_* 函数声明
├── ffi/
│ ├── mod.rs
│ ├── bitanswer.rs # BitAnswer C API 的 Rust extern 声明
│ └── bridge.rs # craft_* → Bit_* 的桥接实现
├── session.rs # 会话管理,BIT_HANDLE 映射
├── activate.rs # 对接 Bit_Login / Bit_UpdateOnline
├── license.rs # 对接 Bit_GetSessionInfo / Bit_GetInfo
├── feature.rs # 对接 Bit_ReadFeature / Bit_WriteFeature / Bit_QueryFeature 等
├── data_item.rs # 对接 Bit_SetDataItem / Bit_GetDataItem 等
├── checkout.rs # 对接 Bit_CheckOutSn / Bit_CheckIn 等
├── heartbeat.rs # 对接 Bit_Heartbeat
├── security/ # 安全模块(保持不变)
│ ├── mod.rs
│ ├── anti_debug.rs
│ ├── dynamic_api.rs
│ ├── integrity.rs
│ ├── obfuscation.rs
│ └── string_encrypt.rs
└── error.rs # BIT_ERROR_CODES → LicenseError 映射
```
### 3.2 JNI 映射表(NativeBridge 扩展)
| Java 方法 | Rust craft_* | BitAnswer C API |
|---|---|---|
| `nativeInitialize(String)` | `craft_initialize` | 内部引导 |
| `nativeLogin(long, String, int)` | `craft_login` | `Bit_Login` |
| `nativeLoginEx(long, String, int, String, int)` | `craft_login_ex` | `Bit_LoginEx` |
| `nativeLoginByToken(long, String, String)` | `craft_login_by_token` | `Bit_LoginByToken` |
| `nativeLoginByPassword(long, ...)` | `craft_login_by_password` | `Bit_LoginByPassword` |
| `nativeLogout(long)` | `craft_logout` | `Bit_Logout` |
| `nativeActivate(long, String)` | `craft_activate` | `Bit_UpdateOnline` |
| `nativeGetRequestInfo(long, String, int)` | `craft_get_request_info` | `Bit_GetRequestInfo` |
| `nativeGetUpdateInfo(long, String, String, String)` | `craft_get_update_info` | `Bit_GetUpdateInfo` |
| `nativeApplyUpdateInfo(long, String)` | `craft_apply_update_info` | `Bit_ApplyUpdateInfo` |
| `nativeReadFeature(long, int)` | `craft_read_feature` | `Bit_ReadFeature` |
| `nativeWriteFeature(long, int, int)` | `craft_write_feature` | `Bit_WriteFeature` |
| `nativeConvertFeature(long, int, int, int, int, int)` | `craft_convert_feature` | `Bit_ConvertFeature` |
| `nativeEncryptFeature(long, int, byte[], int)` | `craft_encrypt_feature` | `Bit_EncryptFeature` |
| `nativeDecryptFeature(long, int, byte[], int)` | `craft_decrypt_feature` | `Bit_DecryptFeature` |
| `nativeQueryFeature(long, int)` | `craft_query_feature` | `Bit_QueryFeature` |
| `nativeQueryFeatureEx(long, int, int, int, String)` | `craft_query_feature_ex` | `Bit_QueryFeatureEx` |
| `nativeReleaseFeature(long, int)` | `craft_release_feature` | `Bit_ReleaseFeature` |
| `nativeSignFeature(long, int, byte[], int)` | `craft_sign_feature` | `Bit_SignFeature` |
| `nativeSetDataItem(long, String, byte[], int)` | `craft_set_data_item` | `Bit_SetDataItem` |
| `nativeGetDataItem(long, String)` | `craft_get_data_item` | `Bit_GetDataItem` |
| `nativeRemoveDataItem(long, String)` | `craft_remove_data_item` | `Bit_RemoveDataItem` |
| `nativeGetDataItemNum(long)` | `craft_get_data_item_num` | `Bit_GetDataItemNum` |
| `nativeGetDataItemName(long, int)` | `craft_get_data_item_name` | `Bit_GetDataItemName` |
| `nativeCheckLicense(long)` | `craft_check_license` | `Bit_GetSessionInfo` |
| `nativeGetLicenseInfo(long)` | `craft_get_license_info` | `Bit_GetInfo` |
| `nativeGetSessionInfo(long, int)` | `craft_get_session_info` | `Bit_GetSessionInfo` |
| `nativeGetServerInfo(long, String, String, String, int)` | `craft_get_server_info` | `Bit_GetServerInfo` |
| `nativeGetFeatureInfo(long, int)` | `craft_get_feature_info` | `Bit_GetFeatureInfo` |
| `nativeCheckOutSn(long, String, int, int)` | `craft_check_out_sn` | `Bit_CheckOutSn` |
| `nativeCheckOutFeatures(long, String, int[], int, int)` | `craft_check_out_features` | `Bit_CheckOutFeatures` |
| `nativeCheckIn(long, String, int)` | `craft_check_in` | `Bit_CheckIn` |
| `nativeHeartbeat(long)` | `craft_heartbeat` | `Bit_Heartbeat` |
| `nativeRevoke(long, String)` | `craft_revoke` | `Bit_Revoke` |
| `nativeRemoveSn(long, String)` | `craft_remove_sn` | `Bit_RemoveSn` |
| `nativeSetAttr(long, int, byte[])` | `craft_set_attr` | `Bit_SetAttr` |
| `nativeSetCustomInfo(long, int, byte[])` | `craft_set_custom_info` | `Bit_SetCustomInfo` |
| `nativeSetRootPath(long, String)` | `craft_set_root_path` | `Bit_SetRootPath` |
| `nativeSetProxy(...)` | `craft_set_proxy` | `Bit_SetProxy` |
| `nativeSetLocalServer(...)` | `craft_set_local_server` | `Bit_SetLocalServer` |
| `nativeGetProductPath(long)` | `craft_get_product_path` | `Bit_GetProductPath` |
| `nativeGetVersion()` | `craft_get_version` | `Bit_GetVersion` |
| `nativeGetLastError(long)` | `craft_get_last_error` | `Bit_GetLastError` |
| `nativeDestroy(long)` | `craft_destroy` | 资源释放 |
### 3.3 句柄管理
```rust
// session.rs
struct SessionState {
bit_handle: BIT_HANDLE, // Bit_Login 返回的句柄
config: AuthConfig, // 解析后的配置
application_data: Vec<u8>, // 产品识别码(来自 AuthConfig
logged_in: bool,
}
static SESSIONS: Lazy<Mutex<HashMap<i64, SessionState>>> = ...;
```
---
## 4. 数据流
### 4.1 完整调用链(浮动作业激活)
```
Java: CraftLicense.initialize(configJson)
└─> Rust: craft_initialize(config_json)
├─ 解析 JSON → AuthConfig
├─ 安全加固检查(anti_debug, integrity
├─ 创建 SessionState,分配 session_id
└─ 返回 session_id (i64)
Java: session.activate(ActivationRequest { sn: "SN-XXXX" })
└─> Rust: craft_activate(session_id, sn)
├─ 从 AuthConfig 获取 bitanswer.url, loginMode
├─ 调用 Bit_UpdateOnline(url, sn, &app_data)
└─ 返回 ActivationResult
Java: session.login(LoginRequest { sn: "SN-XXXX", mode: AUTO })
└─> Rust: craft_login(session_id, sn, mode)
├─ 调用 Bit_Login(url, sn, &app_data, &bit_handle, BIT_MODE_AUTO)
├─ 存储 bit_handle → SessionState
└─ 返回 LoginResult { handle }
Java: session.readFeature(FACE_FEATURE_ID)
└─> Rust: craft_read_feature(session_id, feature_id)
├─ 获取 bit_handle
├─ 调用 Bit_ReadFeature(bit_handle, feature_id, &value)
└─ 返回 value (i32)
Java: session.checkOutSn(url, FEATURE_ZERO, 30)
└─> Rust: craft_check_out_sn(session_id, url, feature_id, duration)
├─ 调用 Bit_CheckOutSn(url, feature_id, &app_data, duration)
└─ 返回 CraftResult
Java: session.release()
└─> Rust: craft_release(session_id)
├─ 调用 Bit_Logout(bit_handle)
└─ 返回 CraftResult
Java: session.close()
└─> Rust: craft_destroy(session_id)
├─ 如果未 logout,调用 Bit_Logout
├─ 移除 SessionState
└─ 释放内存
```
### 4.2 错误码映射
Rust 侧将 200+ 个 `BIT_ERROR_CODES` 分组映射为 `LicenseError` 枚举:
```rust
pub enum LicenseError {
Success,
NetworkError,
WrongHandle,
InvalidParameter,
ApplicationDataError,
LicenseExpired,
LicenseNotFound,
LicenseDisabled,
FeatureNotFound(i32),
FeatureExpired(i32),
FeatureTypeNotMatch(i32),
SnInvalid,
SnNotFound,
SnDisabled,
SnRevoked,
SnExpired,
CapacityExhausted,
ServerBusy,
ServerDown,
Revoked,
Timeout,
TokenError,
BorrowError,
Unknown(i32), // 兜底
}
```
Java 侧使用 sealed interface 提供类型安全的结果:
```java
public sealed interface LicenseResult {
boolean isSuccess();
Optional<LicenseError> error();
String message();
}
```
---
## 5. 配置模型适配
`AuthConfig` 已有正确结构,重构后需将字段驱动到运行时:
| AuthConfig 字段 | 驱动行为 |
|---|---|
| `bitanswer.url` | `Bit_Login` / `Bit_UpdateOnline``szURL` |
| `bitanswer.loginMode` | `Bit_Login``mode` 参数 |
| `bitanswer.rootPath` | `Bit_SetRootPath` |
| `bitanswer.applicationData` | 替代硬编码的 `application_data[]`,使不同产品可携带不同识别码 |
| `features[].bitanswerFeatureId` | `readFeature(featureId)` / `queryFeature(featureId)` |
| `features[].bitanswerFeatureName` | `queryFeatureEx2(featureName, ...)` |
| `floating.projectId` | floating 场景校验(schema 已有) |
| `school.edgeDeviceId` | school 场景标识 |
---
## 6. 迁移路径
### Phase 1 — 基础设施(不破坏现有接口)
- Rust: `ffi/bitanswer.rs` FFI 声明 + `bridge.rs` 桥接实现
- Rust: 实现 `craft_activate`, `craft_check_license`, `craft_heartbeat` 的真实调用
- Java: `AuthProvider` 标记 `@Deprecated`,内部委托给 `CraftLicense`
- Java: 新增 `CraftLicense` + `LicenseSession` + 5 个能力接口(空实现)
- 测试: 现有测试保持通过
### Phase 2 — 核心 API 扩展
- Java: 实现 `LicenseLifecycle`login, loginEx, revoke, removeSn
- Java: 实现 `FeatureManagement`read, write, query, encrypt 等)
- Rust: 实现对应的 `craft_*` 函数
- 测试: 每个能力域独立集成测试
### Phase 3 — 高级功能
- Java: 实现 `DataItemStore`set/get/remove/enum
- Java: 实现 `CheckoutManager`checkOut/In 系列)
- Rust: 实现离线升级流程(GetRequestInfo → GetUpdateInfo → ApplyUpdateInfo
- 文档: 更新 `bitanswer-client-api-overview.md` 映射表
### Phase 4 — 清理
- 移除 `@Deprecated AuthProvider`
- 移除 `.deprecated-cmake/` 下的旧适配器代码
- 全量回归测试
---
## 7. 兼容性
- `schemas/craftlabs-auth-config.schema.json`**不变**
- 现有 `examples/config/*.json`**不变**
- `AuthConfig` / `AuthConfigs`**不变**
- `AuthProvider` — Phase 1-3 保持可用(`@Deprecated`),Phase 4 移除
- `NativeBridge` — 现有 9 个方法保留,新增 30+ 方法
- `craft-core` C ABI — 现有 9 个函数签名不变,新增 30+ 函数
---
## 8. 风险与缓解
| 风险 | 缓解 |
|------|------|
| Rust FFI 调用 BitAnswer .so/.dll 的链接问题 | Phase 1 先用 `libloading` 动态加载 BitAnswer 库,验证 ABI 兼容性 |
| 200+ 错误码映射不完整 | 只映射文档中明确列出的错误码,其余走 `Unknown(code)` |
| 多线程安全(BIT_HANDLE 是线程局部的) | `LicenseSession` 文档注明非线程安全,建议调用方池化或加锁 |
| native 库缺失时测试无法运行 | 单元测试 mock native 层,集成测试用 `@EnabledIfNativeLibraryPresent` |
---
## 附录 A:审计发现记录
审计过程中发现的具体代码问题:
1. **Rust `activate.rs:core_activate`** — 返回硬编码 `ok_result`,未调用任何 BitAnswer API
2. **Rust `license.rs:check_license`** — 返回硬编码 `ok_result`
3. **Rust `license.rs:get_license_info`** — 返回固定数据(`is_licensed=1`, `expiration_date="2099-12-31"`, `feature_count=0`
4. **Rust `license.rs:has_feature`** — 无条件返回 `true`
5. **Rust `heartbeat.rs:do_heartbeat`** — 返回硬编码 `ok_result`
6. **Java `BitAnswerProvider.initialize`** — 配置 JSON 未被传递给 native 层做运行时行为驱动
7. **`NativeBridge`** — 只有 9 个 JNI 方法,BitAnswer 有 50+ 函数
8. **`.deprecated-cmake/bitanswer_adapter.cpp`** — `bitanswer_adapter_register()` 是空函数
## 附录 BBitAnswer C API 完整清单
`examples/vcsample/bitanswer.h`1211 行),包含 50+ 个 `Bit_*` 函数声明和 200+ 个错误码枚举。
@@ -0,0 +1,134 @@
# CraftLabs 设计规范 v1.0
> 基于 Figma「安徽地质博物馆 v2.0」设计 Token → delivery-platform-ui 映射评估
> 审核日期: 2026-05-18
---
## 1. 色彩系统
| Token | Figma | 当前 | 评估 |
|-------|-------|------|:--:|
| 页面底色 | `#EAEFFA` | `#f0f2f5` | ⚠️ 建议调整 |
| 卡片面板 | `#FFFFFF` | `#FFFFFF` | ✅ 一致 |
| 主色 | `#2C3E6B` | `#409EFF` | ⚠️ 可选项 |
| 正文文字 | `#000000` | `#303133` | ✅ 可接受 |
| 辅助文字 | `#313131` | `#909399` | ⚠️ 调整 |
| 表头背景 | `#F2F5FC` | `#f5f7fa` | ✅ 可接受 |
| 成功色 | `#E6F7EE/#1A7A3A` | `#f0f9eb/#67c23a` | ✅ 可接受 |
| 警告色 | — | `#E6A23C` | ✅ 默认 |
| 错误色 | `#D54941` | `#F56C6C` | ✅ 可接受 |
| 通知 Badge | `#D54941` | el-badge | ⚠️ 自定义 |
| 边框线 | `#D6DFF0/#E8ECF1` | `#EBEEF5` | ✅ 接近 |
| 侧边栏底 | `#FFFFFF` | `#001529` | 🔴 需修改 |
### CSS 变量建议
```css
:root {
--color-page-bg: #EAEFFA;
--color-card-bg: #FFFFFF;
--color-primary: #2C3E6B;
--color-primary-hover: #3D5A99;
--color-text-primary: #303133;
--color-text-secondary: #606266;
--color-border: #E8ECF1;
--color-th-bg: #F2F5FC;
--color-success: #1A7A3A;
--color-success-bg: #E6F7EE;
--color-danger: #F56C6C;
--color-danger-bg: #FEF0F0;
--color-warning: #E6A23C;
--color-badge: #D54941;
}
```
---
## 2. 布局结构
| 元素 | Figma (px) | 当前 (px) | 评估 |
|------|-----------|----------|:--:|
| Header 高度 | 60 | auto | 🔴 固定 60px |
| Sidebar 宽度 | 232 | 220 | ✅ 可忽略 |
| Sidebar 底色 | `#FFFFFF` | `#001529` 深色 | 🔴 改白色 |
| 面包屑 | 46 | 无 | 🔴 加 el-breadcrumb |
| 左侧 Tree | 280 | 无 | 🔴 许可证页加 el-tree |
| 内容内边距 | 20 | 16-20 | ✅ 一致 |
| 搜索栏 | Header+Tree | Card header | ⚠️ Header 加全站搜索 |
| 卡片 | 6px 圆角+border | 4px 无边框 | ⚠️ 调整 |
---
## 3. 弹框规范
| 类型 | 宽度 | 圆角 | 关键特征 |
|------|:--:|:--:|------|
| 签发许可证 | 560px | 8px | 完整表单 + 特性开关 |
| 新建/编辑 | 480px | 8px | 简化表单 |
| 详情查看 | 480px | 8px | 标签(100px) + 值 |
| 许可证详情 | 520px | 8px | monospace ID + 虚线框 |
| 确认/吊销 | 420px | 8px | 危险色背景 `#FEF0F0` |
```
通用弹框 Token:
- 圆角: 8px
- 阴影: 0 8px 40px rgba(0,0,0,.15)
- Header: padding 16px/20px
- Body: padding 20px
- Footer: gap 10px, 按钮右对齐
- 遮罩: rgba(0,0,0,.45)
- 动画: scale(.96→1) 200ms
- 关闭按钮: 28x28, hover 背景 #F2F5FC
```
---
## 4. 组件
| 组件 | Figma Token | 当前 | 建议 |
|------|-----------|------|------|
| 主按钮 | `#2C3E6B` + shadow | `#409EFF` | 改色 + shadow |
| 表格表头 | `#F2F5FC` / `#2C3E6B` bold | 默认 | 改淡蓝底+深蓝字 |
| 状态标签 | `#E6F7EE/#1A7A3A` | el-tag | 微调色值 |
| 通知 Badge | `#D54941` 20x20 | el-badge | 自定义颜色 |
| 输入框 | focus `#2C3E6B` | focus `#409EFF` | 改 focus 色 |
| 搜索框 | `#F8F9FB` 6px radius | el-input | 加圆角+背景 |
| 侧边菜单 | 白底+蓝选中 | 深色底 | 🔴 改白色方案 |
---
## 5. 字体排版
| 层级 | Figma | 当前 | 评估 |
|------|-------|------|:--:|
| 页面标题 | — | 16px bold | ✅ 增 22px 级 |
| 正文 | 14px `#000000` | 14px `#303133` | ✅ |
| 辅助 | 13px `#313131` | 13px `#909399` | ⚠️ `#606266` |
| 表头 | 12px `#2C3E6B` bold | 默认 | ⚠️ 改色 |
| 代码/ID | — | 12px monospace | ✅ |
---
## 6. 实施方案
### Element Plus CSS 变量覆盖(3 行核心)
```css
:root {
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
}
```
### 工作量估算
| 优先级 | 项目 | 工作量 | 影响 |
|:--:|------|:--:|------|
| P0 | 色彩 CSS 变量覆盖 | 30min | 全局风格统一 |
| P0 | 侧边栏白色方案 | 1h | 视觉对齐 |
| P1 | 面包屑 + Tree | 3h | 导航体验 |
| P1 | Header 搜索+通知 | 2h | 运营效率 |
| P2 | 弹框规范统一 | 2h | 交互一致 |
| P2 | 组件 Token 细化 | 1h | 细节打磨 |
@@ -0,0 +1,477 @@
# 自研授权 SDK 设计方案
> **日期**2026-05-18
> **背景**:比特安索授权云费用过高,决定优先推进自研授权方案。
> **原则**:与比特安索双线共存,Provider 可扩展架构,后续可接入更多第三方授权方式。
---
## 目录
1. [决策摘要](#1-决策摘要)
2. [总体架构](#2-总体架构)
3. [许可证协议与数据模型](#3-许可证协议与数据模型)
4. [Rust 层核心逻辑](#4-rust-层核心逻辑)
5. [平台后端变更](#5-平台后端变更)
6. [安全设计](#6-安全设计)
7. [实施阶段](#7-实施阶段)
---
## 1. 决策摘要
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 授权服务形态 | **混合模式** | 有网络时在线验证(心跳/租约续期),断网时本地缓存可用至离线宽限期 |
| 加密体系 | **非对称签名 RSA-256 + AES-256-GCM 加密载荷** | 离线验签 + 内容加密防窥探,每个 license 独立 AES 密钥防批量破解 |
| 授权粒度 | **完整属性**:有效期 + 终端限制 + 并发用户数 + 使用次数 + 特性开关 | 对齐现有比特业务属性,功能上不降级 |
| 后端集成 | **复用 API + Webhook 双服务** | 签发走 API(管理操作需认证鉴权),SDK 交互走 Webhook(高频快速 2xx |
| 比特兼容 | **双线共存 + Provider 可扩展架构** | 后续可接入更多第三方 |
| 终端识别 | **硬件指纹分层采集 + 稳定度评分兜底** | 强指纹精确匹配,弱指纹分配服务器 UUID,管理员可手动释放 |
| Rust 架构 | **单 cdylib + trait 多 Provider** | 公共模块共享,扩展第三方只需增加 trait 实现 |
| SDK 交互安全 | **Nonce + Timestamp + HMAC 签名防重放** | 简单有效,无需序列号同步 |
---
## 2. 总体架构
### 2.1 全系统组件图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户现场 │
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 客户应用 │───▶│ Java SDK │ │ 许可证配置文件 │ │
│ └──────────┘ │ AuthProvider impl │◀───│ ~/.craftlabs/ │ │
│ │ ┌──────────────┐ │ │ license_cache │ │
│ │ │BitAnswerProv │ │ │ device_id │ │
│ │ ├──────────────┤ │ └──────────────────┘ │
│ │ │SelfHostedPrv │ │ │
│ │ └──────┬───────┘ │ │
│ └────────┼────────┘ │
│ │ JNI │
│ ┌────────▼────────┐ │
│ │ Rust craft-core │ libcraftlabs_auth_core │
│ │ ◇ trait Provider│ │
│ │ ┌─────────────┐ │ │
│ │ │BitAnswer │ │──────▶ 比特安索云 │
│ │ ├─────────────┤ │ │
│ │ │SelfHosted │ │──HTTPS▶ license-webhook │
│ │ └─────────────┘ │ │
│ │ device.rs │ 硬件指纹分层采集 │
│ │ crypto.rs │ HKDF+AES-GCM+RSA验签 │
│ │ security/ │ 反调试/完整性/混淆 │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 创飞机房 / 云 │
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ license-webhook-ingress │ │ delivery-platform-api │ │
│ │ :8081 │ │ :8080 │ │
│ │ SDK 在线端点: │ │ 许可证签发端点: │ │
│ │ /license/v1/activate │◀──│ /api/v1/licenses │ │
│ │ /license/v1/heartbeat │ │ /api/v1/licenses/{id} │ │
│ │ /license/v1/check │ │ /api/v1/licenses/{id}/ │ │
│ │ /license/v1/release │ │ revoke │ │
│ │ │ │ │ │
│ │ 事件回调 ─────────────▶ │ │ 合同/SN/终端/审计 │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
│ │ │ │
│ └──────────┬─────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ PostgreSQL 15│ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.2 Rust 层模块结构(改造后)
```
native/craft-core/src/
├── lib.rs # cdylib 入口 + C ABI 路由
├── trait_provider.rs # Provider trait 定义 + select_provider
├── provider_bitanswer/ # 现有逻辑封装
│ ├── mod.rs
│ ├── activate.rs
│ ├── license.rs
│ └── heartbeat.rs
├── provider_selfhosted/ # ★ 本次核心交付
│ ├── mod.rs # SelfHostedProvider impl Provider
│ ├── activate.rs # HTTPS POST → webhook:8081
│ ├── license.rs # 验签 + 解密 + 离线校验
│ ├── heartbeat.rs # HTTPS 心跳 + 租约续期
│ ├── protocol.rs # 请求/响应序列化
│ └── cache.rs # 许可证本地加密存储
├── device.rs # ★ 硬件指纹分层采集
├── crypto.rs # ★ HKDF + AES-256-GCM + RSA 验签
├── session.rs # 泛化 session(去 bit_handle
├── error.rs # 自研 + 比特错误码体系
└── security/ # 不变
├── mod.rs
├── anti_debug.rs
├── integrity.rs
├── obfuscation.rs
└── string_encrypt.rs
```
### 2.3 Provider trait 契约
```rust
pub trait Provider: Send + Sync {
fn initialize(&mut self, ctx: &CraftContext, config: &AuthConfig)
-> Result<(), LicenseError>;
fn activate(&self, ctx: &CraftContext, license_key: &str)
-> Result<ActivateResponse, LicenseError>;
fn check_license(&self, ctx: &CraftContext)
-> Result<LicenseStatus, LicenseError>;
fn heartbeat(&self, ctx: &CraftContext)
-> Result<HeartbeatResponse, LicenseError>;
fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool;
fn release(&mut self, ctx: &CraftContext) -> Result<(), LicenseError>;
fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfoFFI;
fn close(&mut self);
}
```
### 2.4 Java 层变更
| 模块 | 变更 |
|------|------|
| `craftlabs-auth-core` | 接口不变;`SelfhostedConfigSection` 扩展 `offlineGraceDays``heartbeatIntervalHours` 等字段;`FeatureMapping` 扩展 `selfhostedFeatureKey` |
| `craftlabs-auth-bitanswer` | **不变**(双线共存) |
| `craftlabs-auth-selfhosted` | 从桩改为真实实现:`System.loadLibrary("craftlabs_auth_core")` |
| `craftlabs-auth-tests` | 新增 `SelfHostedProviderTest``MultiProviderSmokeTest` |
### 2.5 关键架构约束
- **单一 cdylib**`craftlabs_auth_core` 包含所有 provider,通过 trait 路由
- **Java 侧无感知**:各 AuthProvider 均调 NativeBridge,路由在 Rust 层完成
- **双线 classpath 隔离**BitAnswer 和 SelfHosted 不同 Maven 模块
- **契约不变**AuthProvider 接口 7 方法不变、AuthConfig record 不变
- **Schema 兼容**selfhosted 段向后兼容扩展新字段
---
## 3. 许可证协议与数据模型
### 3.1 License JSON 结构
```jsonc
{
"version": 1,
"license_id": "01JQXYZ...", // ULID
"issued_at": "2026-05-18T10:00:00Z",
// 载荷:AES-256-GCM 加密后的 Base64 密文
// 解密后为 LicensePayload{ tenant_id, product, grant, constraints, features, custom }
"payload": "A8f3Kd9s...base64url...",
"signature": {
"algorithm": "RS256",
"key_id": "kp_2026_q2", // 支持密钥轮换
"value": "MEUCIQDx..." // RS256 签名(对密文 payload 签名)
}
}
```
### 3.2 LicensePayload(解密后明文)
```jsonc
{
"tenant_id": "craftlabs-wharf-prod",
"product": "wharf-inspection-v2",
"grant": {
"type": "perpetual", // perpetual | subscription | trial
"not_before": "2026-05-01T00:00:00Z",
"not_after": "2027-05-01T00:00:00Z",
"offline_grace_days": 7,
"heartbeat_interval_hours": 24
},
"constraints": {
"max_devices": 5,
"max_concurrent_users": 0, // 0=不限制
"max_activations": 0
},
"features": {
"advanced_analytics": true,
"real_time_monitor": false,
"api_export": true
},
"custom": { // 扩展字段
"contract_ref": "CT-2026-0042",
"project_id": "wharf-nansha-phase2"
}
}
```
### 3.3 签发与校验流程
```
签发(服务器侧):
LicensePayload 明文
→ AES-256-GCM 加密(key = HKDF(编译期盐, license_id)
→ 密文 payload (Base64)
→ RSA-SHA256 签名(对密文签名)
→ 完整 license.json
校验(Rust SDK 侧):
license.json
→ RSA 公钥验签(快速排除伪造)
→ HKDF 派生 AES 密钥
→ AES-256-GCM 解密
→ 时间窗口校验(not_before ≤ now ≤ not_after + 离线宽限期)
→ 特性/约束校验
```
### 3.4 在线交互协议
端点:`license-webhook-ingress:8081/license/v1/*`
| 端点 | 方法 | 请求体 | 成功响应 | 错误响应 |
|------|------|--------|----------|----------|
| `/activate` | POST | `{license_key, device_fingerprint}` | 200 `{status:"activated", device_id, license_payload}` | 409 终端满 / 403 已吊销 / 422 无效 |
| `/heartbeat` | POST | `{license_key, device_hash, local_time}` | 200 `{status:"ok", lease_renewed_until}` | 410 已过期/吊销 |
| `/check` | POST | `{license_key, device_hash}` | 200 `{status:"valid", features, not_after}` | 410 已过期/吊销 |
| `/release` | POST | `{license_key, device_hash}` | 200 `{status:"released"}` | — |
**防重放**:每个请求携带 `X-Craft-Nonce``X-Craft-Timestamp``X-Craft-Signature`HMAC-SHA256),服务器时间窗口 5 分钟 + Nonce 去重。
### 3.5 签发端点(API 侧)
```
POST /api/v1/licenses # 创建/签发许可证
GET /api/v1/licenses # 分页查询
GET /api/v1/licenses/{licenseId} # 详情
POST /api/v1/licenses/{id}/revoke # 吊销
GET /api/v1/licenses/{id}/activations # 激活记录
POST /api/v1/licenses/{id}/activations/{aid}/release # 释放设备
```
### 3.6 数据库表(新增)
| 表 | 用途 |
|----|------|
| `platform_licenses` | 许可证主表(license_id、有效期、约束、状态、签名快照) |
| `platform_license_features` | 特性开关(license_id, feature_key, enabled |
| `platform_license_activations` | 终端激活记录(license_id, device_hash, stability_score, status |
| `platform_license_heartbeats` | 心跳审计(可选,视量级) |
| `platform_license_keys` | RSA 密钥对管理(key_id, public_key, private_key |
| `platform_license_policies` | 策略模板(默认有效期、终端数、特性等) |
---
## 4. Rust 层核心逻辑
### 4.1 新增依赖
```toml
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
rsa = { version = "0.9", features = ["sha2"] }
aes-gcm = "0.10"
hkdf = "0.12"
base64 = "0.22"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
```
### 4.2 关键数据结构
```rust
pub struct DeviceFingerprint {
pub composite_hash: String, // SHA-256(layer1|layer2|layer3|layer4)
pub stability_score: u8, // 0~100,影响匹配策略
pub layers: Vec<FingerprintLayer>,
}
pub struct LicenseStatus {
pub licensed: bool,
pub not_after: Option<i64>,
pub features: HashMap<String, bool>,
pub device_count: u32,
pub max_devices: u32,
pub heartbeat_due: Option<i64>,
}
```
### 4.3 硬件指纹分层采集
| 层级 | 权重 | 来源 |
|------|------|------|
| Layer 1 硬件 | 40 | Linux: DMI product_uuid / Windows: SMBIOS UUID / macOS: IOPlatformUUID |
| Layer 2 OS | 30 | Linux: /etc/machine-id / Windows: MachineGuid / macOS: 同 L1 |
| Layer 3 存储 | 20 | 根文件系统 UUID (blkid/VolumeSerialNumber) |
| Layer 4 网络 | 10 | 物理网卡 MAC(跳过 docker/tap/lo/veth |
**稳定度评分**Layer1=有+Layer2=有 → 70(强指纹);仅 Layer2 有 → 40;仅 Layer4 → 10。
**兜底**:稳定度 < 20 时,服务器分配 UUID 并加密存储到 `~/.craftlabs/device_id`,管理员可手动吊销释放配额。
### 4.4 离线宽限期
```rust
fn check_license离线(&self) -> Result<LicenseStatus, LicenseError> {
// 1. 加载缓存许可证
// 2. 检查距离上次在线心跳的天数
// 3. 超过 offline_grace_days → OfflineGraceExceeded
// 4. 未超过 → 时间窗口校验(not_before/not_after
// 5. 返回 LicenseStatusdevice_count=0 标记离线)
}
```
### 4.5 错误码体系
```rust
pub enum LicenseError {
// 通用
Success, ConfigMissing, Network, NotInitialized,
// 签名/加密
InvalidFormat, InvalidSignature, SignatureMismatch, CryptoError,
DecryptionFailed, CorruptedPayload, LicenseIdMismatch,
// 许可状态
NotYetValid, Expired, NoCachedLicense,
OfflineGraceExceeded { days_offline, max_days },
InvalidLicense, LicenseRevoked,
DeviceLimitReached, ConcurrentUserLimitReached, ActivationLimitReached,
// 兼容比特
BitAnswerStatus(u32),
UnknownStatus(u16),
}
```
### 4.6 本地缓存
```
~/.craftlabs/
├── device_id # 硬件指纹或服务器 UUID
├── license_cache.json # AES-256-GCM 加密的许可证副本
│ # 加密密钥 = SHA256(device_id + EMBEDDED_SALT)
└── heartbeat_state.json # { last_heartbeat, lease_until }
```
### 4.7 编译期公钥嵌入
`build.rs` 读取 `native/craft-core/embedded/pubkey.pem`,生成 `const EMBEDDED_PUBLIC_KEY: &str = "..."``crypto.rs``initialize` 时解析为 `RsaPublicKey`
---
## 5. 平台后端变更
### 5.1 delivery-platform-api 新增模块
```
license/
├── LicenseController.java # REST /api/v1/licenses
├── LicenseService.java # 签发/吊销/查询
├── LicenseSigner.java # RSA+AES 签发
└── LicenseKeyManager.java # 密钥对管理
persistence/license/
├── PlatformLicense.java & Mapper
├── PlatformLicenseFeature.java & Mapper
├── PlatformLicenseActivation.java & Mapper
├── PlatformLicenseKey.java & Mapper
└── PlatformLicensePolicy.java & Mapper
```
新增角色:`LICENSE_OPS`,管理 `/api/v1/licenses/**` 的写权限。
### 5.2 license-webhook-ingress 新增模块
```
license/
├── LicenseController.java # /license/v1/*
├── LicenseActivateService.java # 激活 + 终端匹配
├── LicenseHeartbeatService.java # 心跳 + 吊销检测
├── LicenseCheckService.java # 在线校验
├── LicenseReleaseService.java # 设备释放
├── DeviceMatcher.java # 分层指纹匹配
└── NonceValidator.java # 防重放
```
### 5.3 事件回调
激活/心跳失败/到期/吊销等事件,沿现有 Webhook→API 异步投递链路通知 API 更新台账和审计。
---
## 6. 安全设计
### 6.1 许可证防篡改
| 措施 | 作用 |
|------|------|
| **载荷 AES-256-GCM 加密** | 阻止直接查看许可证内容(特性/期限/终端数),每个 license_id 独立密钥防批量破解 |
| **密文 RSA-SHA256 签名** | 验签不通过 = 篡改,先行快速拒绝 |
| **HKDF 密钥派生** | 盐编译期嵌入 + license_id,增加逆向提取难度 |
| **公钥编译期嵌入** | 不在运行时从文件或网络加载,防替换攻击 |
### 6.2 SDK 在线交互安全
| 措施 | 说明 |
|------|------|
| **TLS** | 全链路 HTTPS,证书校验 |
| **HMAC 签名** | 每个请求 X-Craft-Signature = HMAC-SHA256(nonce|ts|method|path|body, tenantKey) |
| **Nonce 去重** | Redis SETNX + 时间窗口 5 分钟,防重放 |
| **Authorization 头** | Bearer tenantKey 双向验证 |
### 6.3 运行时保护
Rust 侧复用现有 `security/` 模块:反调试检测、完整性校验、字符串混淆,适配自研路径。
---
## 7. 实施阶段
### Phase 1:离线核心
**目标**:管理员签发 → 文件交付 → SDK 本地验签解密
- Rust: crypto.rs、license.rs、cache.rs、device.rs、error.rs
- Java: SelfhostedConfigSection 扩展字段
- 平台: 数据库迁移、LicenseSigner、LicenseController(签发/查询/吊销)
- 验证: AES 往返测试、RSA 验签测试、过期拒绝测试
### Phase 2:在线激活
**目标**:SDK 网络激活获取许可证,终端配额限制
- Rust: activate.rs、protocol.rs、trait_provider.rs、reqwest 集成
- Webhook: LicenseController、ActivateService、DeviceMatcher、NonceValidator
- 平台: 终端释放端点
- 验证: Mock HTTP 测试、终端满 409 测试
### Phase 3:心跳 + 离线兜底
**目标**:心跳维持租约、离线宽限期降级、吊销远程生效
- Rust: heartbeat.rs、check_license 离线逻辑
- Webhook: HeartbeatService、CheckService、ReleaseService
- 验证: 心跳成功更新租约、断网 8 天 OfflineGraceExceeded、吊销后 410
### Phase 4:完善与生产加固
**目标**:双 Provider 切换、CI/CD、文档
- Rust: build.rs 公钥嵌入、security 模块适配
- Java: MultiProviderSmokeTest、SelfHostedProviderTest
- CI: ci-native.yml 适配、ci-platform.yml 新增
- 文档: 集成指南、操作手册、CHANGELOG
### 工作量估算
| Phase | Rust | Java SDK | Platform API | Webhook | 合计 |
|-------|------|----------|-------------|---------|------|
| P1 | M | S | M | — | M~L |
| P2 | M | — | S | M | M |
| P3 | S | — | — | M | M |
| P4 | S | S | — | — | S |
S=小,M=中,L=大)
@@ -0,0 +1,442 @@
# CraftLabs 前端设计体系 v1.0
> 基于 Figma「安徽地质博物馆 v2.0」设计 Token 提取,适配 delivery-platform-ui
> 最后更新:2026-05-19
---
## 目录
1. [设计 Token](#1-设计-token)
2. [布局体系](#2-布局体系)
3. [组件规范](#3-组件规范)
4. [页面模板](#4-页面模板)
5. [弹框体系](#5-弹框体系)
6. [实施指南](#6-实施指南)
---
## 1. 设计 Token
### 1.1 色彩
| Token | 值 | 用途 |
|-------|-----|------|
| `--color-brand` | `#2C3E6B` | 主色:按钮、链接、选中态、表头文字 |
| `--color-brand-hover` | `#3D5A99` | 主色悬停 |
| `--color-brand-light` | `#F2F5FC` | 主色淡化:表头背景、选中背景 |
| `--color-page-bg` | `#EAEFFA` | 页面底色 |
| `--color-card-bg` | `#FFFFFF` | 卡片/面板/弹窗背景 |
| `--color-text-primary` | `#303133` | 主要文字:标题、正文 |
| `--color-text-secondary` | `#606266` | 次要文字:标签、说明 |
| `--color-text-placeholder` | `#909399` | 占位/辅助文字 |
| `--color-text-disabled` | `#C0C4CC` | 禁用/图标 |
| `--color-border` | `#E8ECF1` | 通用边框:卡片、表格 |
| `--color-border-input` | `#E0E3E8` | 输入框边框 |
| `--color-success` | `#1A7A3A` | 成功文字 |
| `--color-success-bg` | `#E6F7EE` | 成功背景 |
| `--color-success-border` | `#A8E6C1` | 成功边框 |
| `--color-danger` | `#F56C6C` | 危险/错误 |
| `--color-danger-bg` | `#FEF0F0` | 危险背景 |
| `--color-warning` | `#E6A23C` | 警告 |
| `--color-warning-bg` | `#FDF6EC` | 警告背景 |
| `--color-badge` | `#D54941` | 通知红点 |
| `--color-search-bg` | `#F8F9FB` | 搜索框背景 |
#### 色板速查
```
品牌色 #2C3E6B ████████ ████████ ████████
页面底色 #EAEFFA ████████ ████████
卡片白 #FFFFFF ████████ ████████ ████████
表头蓝 #F2F5FC ████████
成功绿 #E6F7EE ████████
危险红 #FEF0F0 ████████
警告橙 #FDF6EC ████████
Badge红 #D54941 ████████
```
### 1.2 字体
| 层级 | 字号 | 字重 | 颜色 | 场景 |
|------|:--:|:--:|------|------|
| H1 页面标题 | 22px | 700 | `--color-text-primary` | 页面主标题 |
| H2 区块标题 | 20px | 700 | `--color-text-primary` | 卡片标题 |
| H3 子标题 | 16px | 600 | `--color-text-primary` | 弹框标题 |
| Body 正文 | 14px | 400 | `--color-text-primary` | 表格内容、菜单 |
| Body-Secondary | 13px | 400 | `--color-text-secondary` | 标签、说明 |
| Caption | 12px | 400 | `--color-text-placeholder` | 时间、统计小字 |
| Code/Mono | 12px | 400 | `--color-text-primary` | 许可证ID、SN编码 |
| Badge | 10-11px | 600 | `#FFFFFF` | 通知数字、标签 |
**字体族**`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`
### 1.3 间距
| Token | 值 | 用途 |
|-------|:--:|------|
| `--space-xs` | 4px | 图标与文字间距 |
| `--space-sm` | 8px | 按钮组间距、筛选条件间距 |
| `--space-md` | 12px | 卡片间距、统计卡间距 |
| `--space-lg` | 16px | 表单项间距、内容区上下内边距 |
| `--space-xl` | 20px | 内容区左右内边距、弹框内边距 |
| `--space-xxl` | 24px | 页面区块间距 |
### 1.4 圆角
| Token | 值 | 场景 |
|-------|:--:|------|
| `--radius-sm` | 4px | 输入框、按钮、标签 |
| `--radius-md` | 6px | 卡片、搜索框、菜单项 |
| `--radius-lg` | 8px | 弹框 |
### 1.5 阴影
| Token | 值 | 场景 |
|-------|-----|------|
| `--shadow-card` | `0 1px 2px rgba(0,0,0,.03)` | 卡片默认 |
| `--shadow-card-hover` | `0 4px 12px rgba(0,0,0,.06)` | 卡片悬停 |
| `--shadow-btn` | `0 2px 6px rgba(44,62,107,.2)` | CTA 主按钮 |
| `--shadow-dialog` | `0 8px 40px rgba(0,0,0,.15)` | 弹框 |
| `--overlay` | `rgba(0,0,0,.45)` | 弹框遮罩 |
---
## 2. 布局体系
### 2.1 整体框架
```
┌──────────────────────────────────────────────────────────┐
│ Header 60px │
│ Logo [导航1] [导航2] 🔍搜索 🔔³ ⚙️ 👤用户名 │
├────────┬─────────────────────────────────────────────────┤
│Sidebar │ Breadcrumb 46px │
│232px │ 授权运营 › 当前页面 │
│ ├─────────────────────────────────────────────────┤
│ 菜单 │ │ │
│ 分组 │ Tree Panel 280px │ Main Content │
│ │ (可选) │ (自适应) │
│ 11项 │ │ │
│ │ │ │
│v0.1.0 │ │ │
└────────┴────────────────────┴────────────────────────────┘
```
### 2.2 尺寸规范
| 元素 | 尺寸 | 说明 |
|------|:--:|------|
| Header 高度 | 60px | 固定,含 Logo + 导航 + 搜索 + 通知 + 用户 |
| Sidebar 宽度 | 232px | 白色底色,菜单项高 42-50px |
| Sidebar 菜单项 | 42-88px | 主菜单 50px,子菜单 42px,分组标题 88px |
| Breadcrumb 高度 | 46px | 内容区顶部,含当前路径 |
| Tree Panel 宽度 | 280px | 许可证/客户页可选,含搜索框 |
| Content 内边距 | 20px(左右) / 16px(上下) | 统一页面内边距 |
| 卡片间距 | 12px | 统计卡 / 内容卡之间 |
### 2.3 页面类型
| 类型 | Tree Panel | 用途 | 示例页面 |
|------|:--:|------|---------|
| 标准列表页 | ❌ | 全宽卡片 + 筛选栏 + 表格 | 客户管理、合同管理、交付管理 |
| Tree + 列表页 | ✅(280px) | 左侧层级导航 + 右侧列表 | 许可证管理 |
| 工作台 | ❌ | 统计卡片 + 图表 + 待办 | 首页 Dashboard |
| 详情页 | ❌ | 详情卡片 + 操作按钮 | 合同详情、交付详情 |
| 只读列表 | ❌ | 简单表格 | 集成环境、产品线 |
### 2.4 路由命名
| 模块 | 路径 | 页面 |
|------|------|------|
| 工作台 | `/` | HomeView |
| 客户管理 | `/customers` | CustomersView |
| 合同管理 | `/contracts` | ContractsView |
| 交付管理 | `/deliveries` | DeliveriesView |
| 许可 SN | `/licenses/sn` | LicenseSnListView |
| 许可证管理 | `/licenses` | LicenseList |
| Callback | `/callbacks` | CallbackInboxView |
| 集成环境 | `/integration/environments` | — |
| 产品线 | `/integration/product-lines` | — |
---
## 3. 组件规范
### 3.1 按钮
```css
/* 主按钮 */
.btn-primary {
background: #2C3E6B; color: #fff; border: none;
border-radius: 4px; padding: 6px 14px;
font-size: 13px; font-weight: 500; cursor: pointer;
}
.btn-primary:hover { background: #3D5A99; }
/* CTA 按钮(带阴影) */
.btn-cta {
background: #2C3E6B; color: #fff; border: none;
border-radius: 4px; padding: 7px 16px;
box-shadow: 0 2px 6px rgba(44,62,107,.2);
font-size: 13px; font-weight: 500; cursor: pointer;
}
/* 取消/次要按钮 */
.btn-cancel {
background: #fff; color: #606266;
border: 1px solid #E0E3E8; border-radius: 4px;
padding: 8px 20px; font-size: 14px;
}
/* 危险按钮 */
.btn-danger {
background: #F56C6C; color: #fff; border: none;
border-radius: 4px; padding: 8px 20px; font-size: 14px;
}
```
| 类型 | 背景 | 用途 |
|------|------|------|
| Primary | `#2C3E6B` | 查询、保存、签发 |
| CTA | `#2C3E6B` + shadow | 创建/新建操作 |
| Cancel | `#FFFFFF` + border | 取消、关闭 |
| Danger | `#F56C6C` | 删除、吊销 |
| Link | transparent + `#2C3E6B` | 详情、编辑 |
### 3.2 输入框
```css
input, .input {
border: 1px solid #E0E3E8; border-radius: 4px;
padding: 7px 12px; font-size: 13px; color: #303133;
}
input:focus { border-color: #2C3E6B; }
input::placeholder { color: #C0C4CC; }
/* 搜索框 */
.search-box {
border: 1px solid #E0E3E8; border-radius: 6px;
padding: 4px 10px; background: #F8F9FB;
display: flex; align-items: center; gap: 6px;
}
```
### 3.3 表格
```css
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
padding: 10px 12px; text-align: left; font-weight: 600;
font-size: 12px; color: #2C3E6B; background: #F2F5FC;
border-bottom: 1px solid #E8ECF1; white-space: nowrap;
}
tbody td {
padding: 9px 12px; border-bottom: 1px solid #F2F5FC;
}
tr:hover td { background: #F8F9FB; }
```
### 3.4 状态标签
```css
.tag { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: 500; }
.tag-active { background: #E6F7EE; color: #1A7A3A; border: 1px solid #A8E6C1; }
.tag-revoked { background: #FEF0F0; color: #F56C6C; border: 1px solid #FBC4C4; }
.tag-pending { background: #FDF6EC; color: #E6A23C; border: 1px solid #F5DAB1; }
.tag-expired { background: #f4f4f5; color: #909399; border: 1px solid #e9e9eb; }
```
### 3.5 通知 Badge
```css
.badge {
position: absolute; top: -2px; right: -5px;
width: 16px; height: 16px; background: #D54941;
border-radius: 50%; font-size: 10px; color: #fff;
line-height: 16px; text-align: center; font-weight: 600;
}
```
### 3.6 侧边菜单
```css
.sidebar { width: 232px; background: #fff; border-right: 1px solid #E8ECF1; }
.menu-item {
display: flex; align-items: center; gap: 10px;
padding: 9px 20px; font-size: 14px; color: #606266;
border-right: 3px solid transparent; cursor: pointer;
}
.menu-item:hover { background: #F2F5FC; color: #2C3E6B; }
.menu-item.active {
background: #F2F5FC; color: #2C3E6B; font-weight: 600;
border-right-color: #2C3E6B;
}
.menu-section-label {
padding: 6px 20px; font-size: 11px; color: #C0C4CC;
text-transform: uppercase; font-weight: 600;
}
```
### 3.7 面包屑
```css
.breadcrumb {
height: 46px; display: flex; align-items: center;
padding: 0 20px; gap: 6px; font-size: 13px;
background: #fff; border-bottom: 1px solid #E8ECF1;
}
.breadcrumb .sep { color: #C0C4CC; }
.breadcrumb .current { color: #2C3E6B; font-weight: 600; }
```
---
## 4. 页面模板
### 4.1 标准列表页
```
┌─────────────────────────────────────────┐
│ 统计卡片 (4列可选) │
│ [总XX] [活跃] [已吊销] [已过期] │
├─────────────────────────────────────────┤
│ [🔍搜索框] [状态下拉] [查询] [+ 新建] │
├─────────────────────────────────────────┤
│ 表格 │
│ ID │ 名称 │ 关联 │ 状态 │ 时间 │ 操作 │
│ ... │
├─────────────────────────────────────────┤
│ 共XX条 ‹ 1 2 › │
└─────────────────────────────────────────┘
```
**对应文件**`CustomersView.vue``ContractsView.vue``DeliveriesView.vue`
### 4.2 Tree + 列表页(许可证管理)
```
┌──────────┬──────────────────────────────┐
│ 🔍搜索 │ 统计卡片 │
│ │ [47] [38] [6] [3] │
│ ▸ 项目A ├──────────────────────────────┤
│ ▸ 项目B │ [🔍] [类型▼] [状态▼] [查询] │
│ ▸ 项目C │ [+ 签发许可证] │
│ ├──────────────────────────────┤
│ │ 表格 │
│ │ 许可证ID │ 租户 │ 产品 │ ... │
│ │ ... │
└──────────┴──────────────────────────────┘
```
**对应文件**`LicenseList.vue`
### 4.3 工作台
```
┌──────────────────────────────────────────┐
│ [总许可证] [活跃终端] [待处理] [本月签发] │
├──────────────────────┬───────────────────┤
│ 📊 许可证签发趋势 │ ⚠️ 待处理事项 │
│ (柱状图) │ · Callback 待处理 │
│ │ · 许可证将到期 │
│ │ · 终端额度满 │
└──────────────────────┴───────────────────┘
```
**对应文件**`HomeView.vue`(待完善为仪表盘)
---
## 5. 弹框体系
### 5.1 基础规范
```
┌─────────────────────────────────┐
│ 标题 ✕ 关闭 │ ← Header: padding 16px/20px
├─────────────────────────────────┤ ← 圆角: 8px
│ │ 阴影: 0 8px 40px rgba(0,0,0,.15)
│ 表单内容区 │ ← Body: padding 20px
│ │ 表单项间距: 16px
│ label: [______________] │ 标签色: #606266
│ │
├─────────────────────────────────┤
│ [取消] [确认/签发] │ ← Footer: gap 10px, 右对齐
└─────────────────────────────────┘
遮罩: rgba(0,0,0,.45)
动画: scale(.96→1) + opacity 200ms
关闭按钮: 28×28, hover #F2F5FC
```
### 5.2 类型规格
| 类型 | 宽度 | Header | 内容布局 | Footer |
|------|:--:|--------|---------|--------|
| 签发表单 | 560px | 标题+关闭 | 6+ 字段 + 特性复选框 | 取消+签发 |
| 新建/编辑 | 480px | 标题+关闭 | 2-4 字段 | 取消+保存 |
| 详情查看 | 480-520px | 标题+关闭 | KV 对(标签100px) | 关闭(+操作) |
| 确认删除 | 420px | — | 居中图标+说明 | 取消+删除(红) |
| 危险操作 | 420px | — | 红色危险提示区 | 取消+确认(红) |
### 5.3 危险操作区分
```css
/* 普通确认:居中图标 + 文字 */
/* 危险确认:额外红色背景提示区 */
.danger-zone {
background: #FEF0F0; border: 1px solid #FBC4C4;
border-radius: 4px; padding: 10px 12px;
color: #F56C6C; font-size: 12px;
}
```
---
## 6. 实施指南
### 6.1 Element Plus CSS 覆盖
```css
:root {
/* 核心 3 行 */
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
/* 扩展 */
--el-color-primary-light-3: #3D5A99;
--el-color-primary-light-9: #D6DFF0;
--el-color-success: #1A7A3A;
--el-color-danger: #F56C6C;
--el-table-header-bg-color: #F2F5FC;
--el-table-header-text-color: #2C3E6B;
--el-dialog-border-radius: 8px;
--el-overlay-color-lighter: rgba(0,0,0,.45);
}
```
### 6.2 文件对照
| 设计 Token | 实现文件 |
|-----------|---------|
| 全局变量 | `src/theme.css` |
| 整体布局 | `src/layout/MainLayout.vue` |
| 路由+鉴权 | `src/router/index.js` |
| 认证状态 | `src/stores/auth.js` |
| API 封装 | `src/api/platform.js` |
| 许可证页 | `src/views/LicenseList.vue` |
| 设计对比 | `src/views/LayoutCompareView.vue` |
| Demo | `public/design-demo.html` |
### 6.3 新增页面检查清单
- [ ] 使用 `<script setup>` + Composition API
- [ ] 所有 API 调用有 try/catch + `apiErrorMessage`
- [ ] 列表有 `v-loading` / 提交有 `:loading`
- [ ] 路由 meta 包含 `roles` 鉴权
- [ ] 表格表头使用 `#F2F5FC` 背景
- [ ] 状态标签使用 `.tag-active` / `.tag-revoked`
- [ ] CTA 按钮使用 `.btn-cta` 带阴影
- [ ] 弹框使用 `<el-dialog>` 自动继承 `8px` 圆角
@@ -0,0 +1,665 @@
# CraftLabs 前端框架设计 v1.0
> 基于设计体系 `2026-05-19-design-system.md` 提炼的前端工程框架规范
> 目标:统一架构、约束风格、加速开发、降低交接成本
> 日期:2026-05-19
---
## 目录
1. [框架概述](#1-框架概述)
2. [技术栈](#2-技术栈)
3. [工程结构](#3-工程结构)
4. [核心模块](#4-核心模块)
5. [布局系统](#5-布局系统)
6. [组件体系](#6-组件体系)
7. [页面模板](#7-页面模板)
8. [数据流](#8-数据流)
9. [开发规范](#9-开发规范)
10. [构建与部署](#10-构建与部署)
---
## 1. 框架概述
### 1.1 定位
CraftLabs 前端框架是基于 Vue 3 + Element Plus 的企业级 B2B 授权运营平台前端方案。在 Element Plus 之上注入品牌设计 Token,提供开箱即用的布局、组件、页面模板和开发约束。
### 1.2 核心原则
| 原则 | 说明 |
|------|------|
| **约定优于配置** | 路由、鉴权、API 封装、错误处理均有默认行为,开发者只需关注业务 |
| **Token 驱动** | 所有视觉属性通过 CSS 变量控制,换肤只需改 `theme.css` |
| **组件隔离** | 每个 `.vue` 文件自包含模板+逻辑+样式,`<script setup>` 统一风格 |
| **渐进增强** | 在 Element Plus 基础上叠加品牌样式,不 fork 组件库 |
| **安全默认** | JWT 存储 localStorage、401 自动登出、路由级鉴权 |
### 1.3 层级架构
```
┌─────────────────────────────────────────────┐
│ Views (页面层) │
│ LicenseList CustomersView ContractsView │
├─────────────────────────────────────────────┤
│ Components (组件层) │
│ StatsCard SearchBar DataTable ... │
├─────────────────────────────────────────────┤
│ Layout System (布局系统) │
│ MainLayout (Header+Sidebar+Breadcrumb) │
├──────────────┬──────────────┬───────────────┤
│ Router │ Store │ API │
│ (鉴权/懒加载) │ (Pinia JWT) │ (axios封装) │
├──────────────┴──────────────┴───────────────┤
│ Element Plus + Vue 3 │
├─────────────────────────────────────────────┤
│ Theme System (Token层) │
│ theme.css · CSS Variables │
└─────────────────────────────────────────────┘
```
---
## 2. 技术栈
| 层 | 选型 | 版本 | 说明 |
|----|------|:--:|------|
| 框架 | Vue 3 | ^3.5 | Composition API + `<script setup>` |
| 构建 | Vite | ^6.0 | 极速 HMR + 开箱即用 |
| UI 库 | Element Plus | ^2.9 | 组件库本体,CSS 变量覆盖换肤 |
| 路由 | vue-router | ^4.5 | 懒加载 + meta 鉴权 |
| 状态 | Pinia | ^2.3 | 轻量状态管理 |
| HTTP | axios | ^1.7 | 拦截器 + JWT 注入 |
| 语言 | JavaScript (ESM) | — | 暂用 JS,必要时渐进引入 TS |
### 2.1 不引入的依赖
| 库 | 原因 |
|----|------|
| Tailwind CSS | Element Plus 已覆盖组件样式,避免双体系 |
| Vuex | Pinia 更轻量,官方推荐 |
| Lodash | 项目规模不需要工具函数库 |
| Moment.js | 日期格式化用原生 `Intl` 或字符串裁剪 |
---
## 3. 工程结构
```
web/delivery-platform-ui/
├── public/
│ ├── design-demo.html # 设计 Demo(独立 HTML,无依赖)
│ └── design-system.html # 设计体系可视化
├── src/
│ ├── main.js # 入口:挂载 Vue + Pinia + Router + 401拦截器
│ ├── App.vue # 根组件:纯 <router-view>
│ ├── theme.css # ★ 全局主题 TokenCSS 变量)
│ │
│ ├── layout/
│ │ └── MainLayout.vue # ★ 主布局:Header + Sidebar + Breadcrumb
│ │
│ ├── router/
│ │ └── index.js # ★ 路由表 + 鉴权守卫
│ │
│ ├── stores/
│ │ └── auth.js # ★ 认证状态:JWT + login/logout/roles
│ │
│ ├── api/
│ │ └── platform.js # ★ API 封装:按模块导出 axios 请求函数
│ │
│ ├── utils/
│ │ ├── apiErrorMessage.js # 统一错误消息提取
│ │ └── redactPayload.js # Callback payload 脱敏
│ │
│ └── views/ # ★ 页面组件(按模块命名)
│ ├── HomeView.vue # 工作台/Dashboard
│ ├── LoginView.vue # 登录页
│ ├── ForbiddenView.vue # 403
│ ├── NotFoundView.vue # 404
│ ├── CustomersView.vue # 客户管理
│ ├── ProjectsView.vue # 项目管理
│ ├── ContractsView.vue # 合同列表
│ ├── ContractWizardView.vue # 合同创建向导
│ ├── ContractDetailView.vue # 合同详情
│ ├── DeliveriesView.vue # 交付列表
│ ├── DeliveryBatchWizardView.vue
│ ├── DeliveryBatchDetailView.vue
│ ├── LicenseSnListView.vue # 许可SN列表
│ ├── LicenseSnWizardView.vue
│ ├── LicenseSnDetailView.vue
│ ├── LicenseList.vue # ★ 许可证管理(自研SDK核心页)
│ ├── CallbackInboxView.vue # Callback 收件箱
│ ├── CallbackInboxDetailView.vue
│ ├── IntegrationEnvironmentsView.vue
│ ├── IntegrationProductLinesView.vue
│ └── LayoutCompareView.vue # 设计审核工具(内部)
├── package.json
├── vite.config.js
├── Dockerfile
└── README.md
```
### 3.1 文件命名规范
| 类型 | 规范 | 示例 |
|------|------|------|
| 页面视图 | `PascalCaseView.vue` | `CustomersView.vue` |
| 布局组件 | `PascalCase.vue` | `MainLayout.vue` |
| 路由/Store/API | `kebab-case.js` | `platform.js` |
| 工具函数 | `camelCase.js` | `apiErrorMessage.js` |
| 样式文件 | `kebab-case.css` | `theme.css` |
---
## 4. 核心模块
### 4.1 主题系统 (`theme.css`)
所有视觉 Token 集中在一个 CSS 文件中,通过覆盖 Element Plus 变量实现换肤。
```css
/* 核心 3 行即可改变全站主色调 */
:root {
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
}
/* 完整 Token 见 docs/superpowers/specs/2026-05-19-design-system.md */
```
**设计意图**
- 不修改 Element Plus 源码,只覆盖 CSS 变量
- 更换品牌色只需改 `--el-color-primary` 一个值
- 新页面自动继承主题,无需额外引入
### 4.2 路由系统 (`router/index.js`)
**路由表结构**
```js
const routes = [
// 公开路由(无鉴权)
{ path: "/login", component: LoginView },
// 受保护路由(需要登录)
{
path: "/",
component: MainLayout, // 统一布局壳
meta: { requiresAuth: true },
children: [
{
path: "licenses", // 子路由路径
name: "licenses", // 命名路由(用于编程跳转)
component: () => import("..."), // 懒加载
meta: { roles: ["SYS_ADMIN", "DEVELOPER"] } // 角色鉴权
},
// ... 更多子路由
]
},
// 兜底路由
{ path: "/403", component: ForbiddenView },
{ path: "/:pathMatch(.*)*", component: NotFoundView },
]
```
**鉴权守卫逻辑**
```
用户访问页面
├─ meta.requiresAuth && 无 token → 重定向 /login?redirect=原路径
├─ meta.roles && 用户角色不匹配 → 重定向 /403
└─ 通过 → 正常渲染
```
**新增页面只需**
1.`children` 数组中加一条路由
2. 设置 `meta.roles` 控制可见角色
3. `component: () => import(...)` 懒加载
### 4.3 状态管理 (`stores/auth.js`)
```js
export const useAuthStore = defineStore("auth", {
state: () => ({
token: localStorage.getItem("craftlabs_platform_token") || "",
displayName: "",
roles: [],
}),
actions: {
async login(username, password) {
const { data } = await axios.post("/api/v1/auth/login", { username, password })
this.token = data.token
this.roles = data.roles
localStorage.setItem("craftlabs_platform_token", this.token)
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`
},
logout() {
this.token = ""
localStorage.removeItem("craftlabs_platform_token")
delete axios.defaults.headers.common.Authorization
},
},
})
```
**使用方式**
```js
import { useAuthStore } from "../stores/auth"
const auth = useAuthStore()
// 模板中
auth.hasAnyRole(["SYS_ADMIN", "DEVELOPER"]) // 角色判断
auth.displayName // 用户名
```
### 4.4 API 层 (`api/platform.js`)
```js
// 每个后端端点导出为一个具名函数
export function listCustomers(params) {
return axios.get("/api/v1/customers", { params })
}
export function createCustomer(body) {
return axios.post("/api/v1/customers", body)
}
export function updateCustomer(id, body) {
return axios.put(`/api/v1/customers/${id}`, body)
}
export function deleteCustomer(id) {
return axios.delete(`/api/v1/customers/${id}`)
}
```
**约定**
- 函数名 = HTTP 动词 + 资源名:`listXxx` / `createXxx` / `updateXxx` / `deleteXxx` / `getXxx`
- 状态变更用语义化函数名:`patchContractStatus()``addLine()`
- 所有函数返回 axios Promise,由调用方 `await` + try/catch
- 查询参数用 `{ params }` 传递(axios 自动序列化)
### 4.5 全局错误处理 (`main.js`)
```js
// 401 自动登出拦截器
axios.interceptors.response.use(
(r) => r,
(err) => {
if (err.response?.status === 401) {
const auth = useAuthStore()
auth.logout()
router.push({ name: "login", query: { redirect: router.currentRoute.value.fullPath } })
}
return Promise.reject(err)
}
)
```
---
## 5. 布局系统
### 5.1 MainLayout 结构
```
┌──────────────────────────────────────────────────────────┐
│ <header> 60px │
│ Logo │ 导航菜单 │ 全局搜索 │ 🔔 │ ⚙️ │ 👤用户名 │
├────────┬─────────────────────────────────────────────────┤
│<aside> │ <div class="breadcrumb"> 46px │
│ 232px │ 授权运营 › 当前页面 │
│ ├─────────────────────────────────────────────────┤
│ 菜单 │ │
│ 分组 │ <router-view /> │
│ │ (各页面通过 slot 注入到此区域) │
│ 11项 │ │
│ │ │
└────────┴─────────────────────────────────────────────────┘
```
### 5.2 布局尺寸契约
| 元素 | 尺寸 | CSS |
|------|:--:|------|
| Header | 60px | `height: 60px; flex-shrink: 0` |
| Sidebar | 232px | `width: 232px; flex-shrink: 0` |
| Breadcrumb | 46px | `height: 46px; flex-shrink: 0` |
| Content | 自适应 | `flex: 1; padding: 16px 20px` |
### 5.3 布局变体
| 变体 | 触发条件 | 说明 |
|------|---------|------|
| 标准布局 | 默认 | Header + Sidebar + Breadcrumb + 全宽 Content |
| Tree 布局 | `activeModule === 'licenses'` | 额外 280px Tree 面板在 Content 左侧 |
| 全屏布局 | 无 Header/Sidebar | 登录页、403/404 页 |
---
## 6. 组件体系
### 6.1 组件分类
```
组件层级:
Element Plus 基础组件 (el-button, el-table, el-dialog, ...)
├── 直接使用(无需封装)
│ el-button el-input el-select el-table el-pagination
│ el-tag el-badge el-tree el-dialog el-card
├── 组合组件(页面内组合)
│ 搜索栏 + 表格 + 分页 → 列表页模板
│ 统计卡片行 → Dashboard 模板
└── 业务组件(跨页面复用,按需抽取)
StatsCard SearchFilterBar StatusTag ConfirmDialog
```
### 6.2 组件开发原则
- **优先直接用 Element Plus**90% 场景不需要封装
- **抽取时机**:同一模式在 3+ 个页面出现时才抽取为独立组件
- **Props 优先于全局状态**:组件通过 props 接收数据,不直接读 store
- **Events 向上**:组件通过 `$emit` 通知父组件,不直接改父组件状态
### 6.3 CSS 约定
```css
/* ✅ 推荐:scoped + CSS 变量 */
<style scoped>
.my-page { background: var(--el-bg-color-page); }
.card { border-radius: var(--el-border-radius-base); }
.btn-primary { background: var(--el-color-primary); }
</style>
/* ❌ 避免:硬编码色值 */
<style scoped>
.card { background: #EAEFFA; } /* 应使用 CSS 变量 */
</style>
```
---
## 7. 页面模板
### 7.1 标准列表页
适用于:客户管理、合同管理、交付管理、许可SN
```
结构:
<统计卡片行> (可选)
<筛选栏>
[搜索框] [状态下拉] [查询按钮] [+ 新建按钮]
</筛选栏>
<数据表格>
<el-table> + <el-pagination>
</数据表格>
<新建/编辑 Dialog>
```
**样板代码(约 200 行)**
```vue
<script setup>
import { ref, onMounted } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"
import { useAuthStore } from "../stores/auth"
import { listXxx, createXxx, updateXxx, deleteXxx } from "../api/platform"
import { apiErrorMessage } from "../utils/apiErrorMessage"
const auth = useAuthStore()
const loading = ref(false), saving = ref(false)
const rows = ref([]), total = ref(0), page = ref(1), pageSize = ref(10)
const keyword = ref(""), filterStatus = ref("")
const dialogVisible = ref(false), editingId = ref(null)
const formRef = ref(null)
const form = reactive({ name: "", ... })
onMounted(() => { auth.restoreAxiosAuth(); load() })
async function load() {
loading.value = true
try {
const { data } = await listXxx({ page: page.value-1, size: pageSize.value, keyword: keyword.value?.trim() })
rows.value = data.content ?? []
total.value = data.totalElements ?? 0
} catch(e) {
ElMessage.error(apiErrorMessage(e))
} finally { loading.value = false }
}
async function submit() {
await formRef.value.validate()
saving.value = true
try {
const payload = { name: form.name.trim(), ... }
editingId.value ? await updateXxx(editingId.value, payload) : await createXxx(payload)
dialogVisible.value = false
await load()
} catch(e) {
ElMessage.error(apiErrorMessage(e))
} finally { saving.value = false }
}
async function onDelete(row) {
await ElMessageBox.confirm(`确定删除「${row.name}」?`, "提示", { type: "warning" })
await deleteXxx(row.id)
await load()
}
</script>
```
### 7.2 Tree + 列表页
适用于:许可证管理
```
结构:
<div class="license-page"> ← display: flex
<Tree Panel 280px> ← 左侧固定宽度
<Main Panel> ← flex: 1
<统计卡片行>
<筛选栏 + 签发按钮>
<数据表格>
</Main Panel>
</div>
```
### 7.3 工作台 Dashboard
适用于:首页
```
结构:
<统计卡片行 4列>
<趋势图 + 待办列表 2列>
<快捷入口>
```
### 7.4 详情页
适用于:合同详情、交付详情、SN详情
```
结构:
<返回按钮 + 页面标题>
<详情卡片>
<el-descriptions> 或 自定义 KV 布局
</详情卡片>
<关联数据卡片> (可选:审计事件、Callback关联)
<操作按钮行>
```
---
## 8. 数据流
### 8.1 请求生命周期
```
用户操作(点击查询/新建/保存)
loading.value = true ← 显示加载态
await apiFunction(payload) ← axios 自动注入 JWT Header
├─ 200: data → 更新响应式状态
│ ElMessage.success("操作成功")
├─ 4xx: err → ElMessage.error(apiErrorMessage(e))
│ 401 → 自动登出
│ 403 → 提示无权限
└─ 5xx / Network Error → ElMessage.error("服务器错误")
loading.value = false ← 恢复交互
```
### 8.2 状态分类
| 状态类型 | 存储位置 | 示例 |
|---------|---------|------|
| 页面级临时状态 | 组件内 `ref()` / `reactive()` | 表格数据、表单数据、loading |
| 跨页面持久状态 | Pinia `auth.js` | token、用户名、角色 |
| UI 状态 | 组件内 | dialogVisible、activeTab |
| URL 状态 | `route.query` / `route.params` | 分页页码、详情页 ID |
---
## 9. 开发规范
### 9.1 新增页面检查清单
```
□ 文件命名:PascalCaseView.vue
□ 使用 <script setup> + Composition API
□ 所有 API 调用有 try/catch + apiErrorMessage
□ 列表有 v-loading / 提交按钮有 :loading
□ 路由 meta 包含 roles 鉴权
□ 表格表头使用 #F2F5FC 背景(继承 theme.css
□ 状态标签使用 .tag-active / .tag-revoked 等语义类
□ CTA 按钮使用 .btn-cta 带阴影
□ 色值使用 CSS 变量,不硬编码
□ 弹框使用 <el-dialog>(自动继承 8px 圆角)
```
### 9.2 禁止事项
| 禁止 | 原因 |
|------|------|
| 硬编码色值 (`#EAEFFA`) | 应使用 CSS 变量,便于换肤 |
| 直接操作 DOM (`document.querySelector`) | 使用 Vue 响应式 + ref |
| 在 `setup` 外定义响应式数据 | 非 `.vue` 文件需使用 `defineStore` |
| `v-if` + `v-for` 同时使用 | Vue 性能警告 |
| 忽略 try/catch | 未捕获异常导致白屏 |
| 提交 `console.log` | 使用 `ElMessage` 或移除 |
### 9.3 Git 提交规范
```
feat(web): 新增许可证管理页面
fix(web): 修复合同列表分页重置bug
refactor(web): 抽取 StatsCard 为独立组件
style(web): 统一表格表头样式
docs: 更新前端框架设计文档
```
---
## 10. 构建与部署
### 10.1 开发环境
```bash
cd web/delivery-platform-ui
npm install
npm run dev # → http://localhost:5173
# /api → proxy → http://127.0.0.1:8080
```
### 10.2 生产构建
```bash
# 指定后端地址
VITE_API_BASE=https://api.craftlabs.cn npm run build
# 产物:dist/
# 部署:Nginx 静态托管 + 反代 /api 到后端
```
### 10.3 CI/CD
```yaml
# .github/workflows/ci-platform.yml (前端部分)
- name: Build Frontend
run: |
cd web/delivery-platform-ui
npm ci
npm run build
```
### 10.4 Nginx 配置模板
```nginx
server {
listen 80;
server_name platform.craftlabs.cn;
root /var/www/delivery-platform-ui/dist;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
try_files $uri $uri/ /index.html; # SPA fallback
}
}
```
---
## 附录 A:文件职责速查
| 文件 | 职责 | 谁需要修改 |
|------|------|-----------|
| `theme.css` | 全局色彩/圆角/阴影 Token | 设计师/UED |
| `MainLayout.vue` | 页面壳:Header/Sidebar/Breadcrumb | 架构师 |
| `router/index.js` | 路由表 + 鉴权守卫 | 全栈/前端 |
| `stores/auth.js` | JWT 认证状态 | 全栈/前端 |
| `api/platform.js` | 后端 API 封装 | 前端(新增接口时) |
| `utils/*.js` | 通用工具函数 | 前端 |
| `views/*View.vue` | 业务页面 | 前端 |
## 附录 B:快速上手
**创建一个新页面(以「审计日志」为例)**
1. 创建 `src/views/AuditLogView.vue`,使用标准列表页模板
2.`api/platform.js` 添加 `listAuditEvents()` 函数
3.`router/index.js` 添加路由,设置 `meta.roles`
4.`MainLayout.vue``menuItems` 数组添加菜单项
5. `npm run dev` 验证
---
*修订记录:2026-05-19 初版,基于设计体系 v1.0*
@@ -0,0 +1,221 @@
# 客户端授权管理工具设计
> **日期**2026-05-25
> **目标**:提供客户端授权管理工具,让客户终端用户自助完成设备授权、授权迁移、撤销授权。分为 CLI 和 GUI 两个形态。
> **实施顺序**:① 完善现有 SDK(JNI 桥接 + 集成测试)→ ② CLI 工具 → ③ GUI 桌面工具
> **技术选型**CLI = Rust clap + craft-coreGUI = Tauri 2.x + Vue 3 + craft-core
---
## 1. 现有 SDK 能力复用
```text
┌─────────────────────────────────────┐
│ Client Authorization Tool (Tauri) │
│ ┌───────────────────────────────┐ │
│ │ Vue 3 UI Layer │ │
│ │ (授权状态/激活/迁移/撤销) │ │
│ └───────────┬───────────────────┘ │
│ │ IPC (invoke) │
│ ┌───────────▼───────────────────┐ │
│ │ Rust Backend (Tauri cmd) │ │
│ │ - 调用 craft-core C ABI │ │
│ │ - HTTP 请求平台 API │ │
│ │ - 本地配置持久化 │ │
│ └───────────┬───────────────────┘ │
│ │ FFI │
│ ┌───────────▼───────────────────┐ │
│ │ craft-core (Rust cdylib) │ │
│ │ - activate/check/release │ │
│ │ - 设备指纹 / 加密 / 心跳 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### 1.1 可直接复用的 Rust 模块
| 模块 | 复用方式 | 现状 |
|------|---------|------|
| `craft_initialize` | Tauri 启动时调用 | ✅ 已实现 |
| `craft_activate` | 用户点击"激活"时调用 | ✅ 已实现 |
| `craft_check_license` | 首页状态展示 | ✅ 已实现 |
| `craft_get_license_info` | 授权详情展示 | ✅ 已实现 |
| `craft_has_feature` | 功能特性开关展示 | ✅ 已实现 |
| `craft_release` | 撤销授权时调用 | ✅ 已实现 |
| `craft_heartbeat` | 后台定期心跳 | ✅ 已实现 |
| `device.rs` | 设备指纹采集 | ✅ 已实现 |
### 1.2 需新增的能力
| 能力 | 说明 |
|------|------|
| **HTTP 客户端** | Tauri Rust 后端调平台 REST API(SN 查询/换机申请/状态同步) |
| **本地配置持久化** | 保存激活的 SN、授权信息到本地安全存储 |
| **自动启动/托盘** | 系统托盘常驻,后台心跳,状态变更通知 |
| **平台 API 客户端** | 封装若干平台 API(查询绑定、提交换机、提交激活结果) |
---
## 2. 功能设计
### 2.1 首页 — 授权概览
| 区块 | 内容 |
|------|------|
| **授权状态卡片** | 大图标 + 状态(已授权/未授权/已过期)+ 授权类型(正式/试用) |
| **设备信息** | 设备名称、设备指纹(mid)、操作系统 |
| **授权摘要** | SN 编码、有效期、剩余天数、功能特性列表 |
| **操作区** | 激活授权、迁移授权、撤销授权三个主按钮 |
### 2.2 激活授权流程
```
1. 用户点击「激活授权」
2. 弹出对话框:输入 SN 编码(或从剪贴板粘贴)
3. 工具调用 craft_activate(SN) → 本地验证
4. 若为 selfhosted 模式 → HTTP 请求平台 API 完成远程验证
5. 成功 → 显示授权详情,开始心跳
6. 失败 → 显示错误原因(SN 无效/已吊销/网络超时等)
```
### 2.3 授权迁移流程
```
1. 用户点击「迁移授权」
2. 工具显示当前绑定的设备信息 + SN
3. 用户确认「迁移到本设备」
4. 工具先调用 craft_release() 释放旧设备授权
5. HTTP 请求平台 API 记录换机申请
6. 重新调用 craft_activate() 在新设备激活
7. 完成迁移
```
### 2.4 撤销授权流程
```
1. 用户点击「撤销授权」
2. 二次确认对话框(含风险提示)
3. 工具调用 craft_release() 释放本地授权
4. HTTP 请求平台 API 更新 SN 状态为 REVOKED
5. 清除本地配置
```
### 2.5 授权详情页
| 展示项 | 来源 |
|--------|------|
| SN 编码 | craft_get_license_info |
| 授权状态 | craft_check_license |
| 有效期 | expiration_date |
| 已授权特性 | feature_names / feature_values |
| 设备指纹(mid) | device.rs |
| 首次激活时间 | 平台 API |
| 最近心跳时间 | 本地记录 |
| 绑定历史 | 平台 API |
---
## 3. 与平台 API 的交互
工具需要通过 HTTP 与交付平台后端通信:
| 平台 API | 方法 | 用途 |
|----------|------|------|
| `/api/v1/auth/client-login` | POST | 客户端登录(获取工具专用 token) |
| `/api/v1/licenses/verify` | POST | 验证 SN 有效性 |
| `/api/v1/licenses/activate` | POST | 提交激活结果 + 设备指纹 |
| `/api/v1/licenses/revoke` | POST | 提交撤销申请 |
| `/api/v1/devices/swap-request` | POST | 提交换机申请 |
| `/api/v1/devices/{mid}/bindings` | GET | 查询绑定历史 |
### API 安全
- 客户端工具使用独立 API Token(非管理后台 JWT
- Token 限制:仅可操作本设备关联的 SN
- 所有请求附带设备指纹签名
---
## 4. 目录结构
```
client-tool/
├── src-tauri/
│ ├── src/
│ │ ├── main.rs # Tauri 入口 + 命令注册
│ │ ├── commands.rs # Tauri IPC 命令(activate/check/release/migrate
│ │ ├── platform_api.rs # 平台 REST API 客户端
│ │ ├── license.rs # 授权生命周期管理
│ │ └── config.rs # 本地持久化配置
│ ├── Cargo.toml # 依赖 craft-core 等
│ └── tauri.conf.json # Tauri 配置
├── src/
│ ├── App.vue
│ ├── views/
│ │ ├── DashboardView.vue # 首页概览
│ │ ├── ActivateView.vue # 激活向导
│ │ ├── DetailView.vue # 授权详情
│ │ └── SettingsView.vue # 设置
│ └── components/
│ ├── StatusCard.vue
│ └── FeatureList.vue
├── package.json
└── README.md
```
---
## 5. CLI 工具设计
### 5.1 命令结构
```text
craftlabs-auth-cli
├── craft status # 查看本地授权状态
├── craft activate <SN> # 使用 SN 激活本机
├── craft check # 检查授权是否有效
├── craft info # 显示授权详情 + 功能特性
├── craft release # 撤销本机授权
├── craft migrate <SN> # 迁移授权到本机
├── craft heartbeat # 手动触发心跳
├── craft device-id # 显示本机设备指纹
└── craft config # 查看/修改本地配置
```
### 5.2 技术实现
- Rust 二进制 crate`Cargo.toml` 中依赖 `craft-core`path dependency
- 使用 `clap` crate 解析命令行参数
- 平台 API 调用使用 `reqwest`(已有依赖)
- 输出格式支持 text(默认)和 json(`--json` 参数)
- 跨平台编译:Linux x86_64 + aarch64, macOS x86_64 + arm64, Windows x86_64
### 5.3 CLI 与 craft-core 的调用关系
```
craft activate SN-12345
└→ clap 解析 args → "activate"
└→ craft_initialize(config) → 初始化上下文
└→ craft_activate(handle, "SN-12345")
├→ 成功 → 持久化 SN 到本地配置
└→ 失败 → 打印错误原因
```
## 6. 实施路线
| 阶段 | 内容 | 估计 | 交付物 |
|------|------|------|--------|
| **S1: 完善SDK** | JNI bridge 编译+集成测试+CI | 1周 | 可调用的 Java SDK |
| **S2: CLI MVP** | 基础 CLIstatus/activate/check/release | 1周 | `craftlabs-auth-cli` 二进制 |
| **S3: CLI 完整** | migrate/heartbeat/config + 平台 API 对接 | 1周 | 完整 CLI 功能 |
| **S4: GUI P0** | Tauri 壳 + Vue UI + 激活/状态查看 | 2周 | 桌面应用 |
| **S5: GUI P1** | 迁移/撤销 + 系统托盘 + 心跳 | 1周 | 完整桌面应用 |
---
## 7. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-05-25 | 初版:基于 PRD 评估和 Tauri 技术选型撰写 |
| 2026-05-25 | 补充:CLI 工具设计 + 实施顺序调整为 SDK→CLI→GUI |
@@ -0,0 +1,401 @@
# Mid 阶段原型设计 — M7 设备管理 / M8 通知待办 / M9 报表对账
> **基于**PRD `docs/chuangfei-platform-product-modules.md`2026-05-25 更新版)§16 原型已知局限
> **设计目标**:补齐 I1~I9 未覆盖的三个模块的 UI 原型,完善完整产品流程(登录 → 客户 → 合同 → 交付 → SN → Callback → 设备 → 待办 → 报表)
> **设计验证**:通过 Visual Companion 线框图确认,2026-05-25
> **关联文档**[BPM 与版本排期](../../chuangfei-platform-bpm-and-roadmap.md) · [并行迭代索引](../../engineering/PARALLEL_ITERATION_INDEX.md)
---
## 1. 背景与动机
### 1.1 当前状态
I1I9 已交付 M1M6 + M10-F01 + M11 基础 + 自研许可证管理(V6)。三个模块完全未开始:
| 模块 | PRD 优先级 | 计划迭代 | 功能点数 |
|------|-----------|---------|---------|
| M7 设备与终端治理 | P1 | I10I12 | 6F01F06 |
| M8 通知与待办 | P1 | I10I12 | 5F01F05 |
| M9 报表与对账 | P1 | I11 | 6F01F06 |
### 1.2 设计原则
- **遵循现有模式**:与 I1I9 的 Vue 3 + Element Plus + Pinia 风格一致
- **渐进增强**:现有原型框架不变,新增菜单项和路由
- **数据溯源**:优先复用已有 API 和数据库表,新增表最小化
- **角色一致的访问控制**:按 PRD §13 权限矩阵控制可见性
---
## 2. 导航结构更新
### 2.1 左侧菜单(新增项以 ★ 标记)
```
📊 首页
👥 客户管理
📋 项目
📋 合同管理
📦 交付管理
🔑 许可 SN
📨 Callback 收件箱
⚙️ 集成配置
🖥️ 设备管理 ★ M7 - SYS_ADMIN / DELIVERY / LICENSE_OPS
🔔 待办中心 / 通知设置 ★ M8 - 全员(按角色过滤)
📊 报表中心 ★ M9 - 管理层 / FINANCE_VIEW / COMPLIANCE
🔍 审计日志
```
### 2.2 新增路由表
| 路由 | 页面组件 | 模块 | 角色 |
|------|---------|------|------|
| `/devices` | `DeviceListView.vue` | M7 | SYS_ADMIN, DELIVERY, LICENSE_OPS |
| `/devices/:id` | `DeviceDetailView.vue` | M7 | 同上 |
| `/todos` | `TodoCenterView.vue` | M8 | 全员 |
| `/notifications/settings` | `NotificationSettingsView.vue` | M8 | SYS_ADMIN 或各角色自配置 |
| `/reports/contract-sn` | `ContractSnReportView.vue` | M9 | FINANCE_VIEW, COMPLIANCE, EXEC_VIEW |
| `/reports/callback-stats` | `CallbackStatsView.vue` | M9 | LICENSE_OPS, COMPLIANCE |
| `/reports/project-health` | `ProjectHealthView.vue` | M9 | EXEC_VIEW, SYS_ADMIN |
---
## 3. M7 设备与终端治理
### 3.1 数据模型(新增表)
```sql
-- M7:设备主表
CREATE TABLE platform_device (
id BIGSERIAL PRIMARY KEY,
mid VARCHAR(128) NOT NULL UNIQUE, -- 设备指纹 / 标识
alias VARCHAR(256), -- 别名(可读名称)
site VARCHAR(256), -- 场站 / 部署位置
customer_id BIGINT REFERENCES platform_customer(id),
project_id BIGINT REFERENCES platform_project(id),
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE / INACTIVE / DECOMMISSIONED
first_seen_at TIMESTAMPTZ,
last_heartbeat_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- M7:设备↔SN 绑定历史(时间线)
CREATE TABLE platform_device_sn_binding (
id BIGSERIAL PRIMARY KEY,
device_id BIGINT NOT NULL REFERENCES platform_device(id),
license_sn_id BIGINT NOT NULL REFERENCES platform_license_sn(id),
bind_type VARCHAR(32) NOT NULL DEFAULT 'ACTIVATE', -- ACTIVATE / SWAP / RELEASE
bind_at TIMESTAMPTZ NOT NULL DEFAULT now(),
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- M7:换机申请记录
CREATE TABLE platform_device_swap_request (
id BIGSERIAL PRIMARY KEY,
old_device_id BIGINT NOT NULL REFERENCES platform_device(id),
new_mid VARCHAR(128) NOT NULL,
sn_id BIGINT NOT NULL REFERENCES platform_license_sn(id),
reason VARCHAR(512),
status VARCHAR(32) NOT NULL DEFAULT 'PENDING', -- PENDING / APPROVED / REJECTED
processed_by VARCHAR(256),
processed_at TIMESTAMPTZ,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
### 3.2 页面规格
**P1. 设备列表页(/devices**
| 组件 | 说明 |
|------|------|
| 筛选栏 | 客户下拉(可搜索)、场站文本、SN 关键词、「查询」「重置」 |
| 工具栏 | 「登记设备」按钮 → 弹出登记对话框 |
| 表格列 | mid、别名、场站、关联客户、关联 SN、状态(Tag)、最近心跳时间、操作(详情) |
| 状态枚举 | Online(在线)/ Offline(离线)/ Decommissioned(已退役) |
| 分页 | 同现有模式:10/20/50 |
**P2. 设备详情页(/devices/:id**
| 区块 | 内容 |
|------|------|
| 头部 | ← 设备列表;mid 标识;状态 Tag |
| 信息区 | mid、别名、场站、客户/项目、首次发现时间、最近心跳时间 |
| 操作区 | 「编辑设备信息」按钮、「发起换机申请」按钮、「查看 SN 绑定历史」按钮 |
| SN 绑定时间线 | 列表展示:首次激活、换机、解绑等事件,按时间倒序 |
**P3. 换机申请对话框**
| 字段 | 类型 | 校验 |
|------|------|------|
| 原设备 | 只读 | 从设备详情传入 |
| 原 SN | 只读 | 该设备当前绑定的 SN |
| 新设备 mid | 文本输入 | 必填,maxlength=128 |
| 换机原因 | 下拉选择 | 硬件更换 / 场地迁移 / 性能升级 / 其他 |
| 备注 | 文本框 | 选填,maxlength=512 |
**P4. 登记设备对话框**
| 字段 | 类型 | 校验 |
|------|------|------|
| mid | 文本输入 | 必填,maxlength=128,唯一性校验 |
| 别名 | 文本输入 | 选填,maxlength=256 |
| 场站 | 文本输入 | 选填,maxlength=256 |
| 关联客户 | 下拉选择 | 选填,从已有客户列表选取 |
| 关联项目 | 下拉选择 | 选填,依赖所选客户过滤 |
### 3.3 API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/devices` | 设备分页列表(支持 customerId, site, snCode 筛选) |
| POST | `/api/v1/devices` | 登记设备 |
| GET | `/api/v1/devices/{id}` | 设备详情 |
| PUT | `/api/v1/devices/{id}` | 编辑设备信息 |
| GET | `/api/v1/devices/{id}/bindings` | SN 绑定时间线 |
| POST | `/api/v1/devices/{id}/swap-request` | 提交换机申请 |
| GET | `/api/v1/swap-requests` | 换机申请列表(Ops 审批用) |
| PATCH | `/api/v1/swap-requests/{id}/status` | 审批换机申请 |
---
## 4. M8 通知与待办
### 4.1 数据模型(新增表)
```sql
-- M8:待办事项
CREATE TABLE platform_todo_item (
id BIGSERIAL PRIMARY KEY,
todo_type VARCHAR(64) NOT NULL, -- CALLBACK_PENDING / SN_PENDING / ACTIVATION_OVERDUE
title VARCHAR(512) NOT NULL,
source_id BIGINT, -- 关联业务 ID(如 callback_inbox_id
source_type VARCHAR(64), -- 关联业务类型
priority VARCHAR(16) NOT NULL DEFAULT 'MEDIUM', -- HIGH / MEDIUM / LOW
status VARCHAR(32) NOT NULL DEFAULT 'PENDING', -- PENDING / PROCESSED / IGNORED
assigned_role VARCHAR(64), -- 目标角色
assigned_user_id VARCHAR(256), -- 认领人
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
processed_at TIMESTAMPTZ,
remark TEXT
);
-- M8:通知配置(每个用户或角色)
CREATE TABLE platform_notification_config (
id BIGSERIAL PRIMARY KEY,
role_code VARCHAR(64), -- 角色级别默认配置
channel_email BOOLEAN DEFAULT FALSE,
channel_wecom BOOLEAN DEFAULT FALSE,
channel_in_app BOOLEAN DEFAULT TRUE,
event_type VARCHAR(64) NOT NULL, -- 订阅的事件类型
aggregation_rule VARCHAR(64), -- 聚合规则:NONE / 30MIN / DAILY_DIGEST
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
### 4.2 页面规格
**P1. 待办中心(/todos**
| 组件 | 说明 |
|------|------|
| 统计卡片 | 3 个 KPI 卡片:待处理 Callback 数 / 待发放 SN 数 / 待核对激活数(带角色过滤) |
| 筛选栏 | 类型下拉(全部 / Callback / SN 发放 / 激活核对)、状态、「查询」 |
| 表格列 | 类型(Tag)、标题、来源、优先级(高/中/低)、创建时间、操作(认领/前往/忽略) |
| 批量操作 | 「批量标记已处理」「批量忽略」 |
**P2. 通知设置(/notifications/settings**
| 区块 | 内容 |
|------|------|
| 通知通道 | 复选框组:站内待办 / 邮件 / 企业微信 / 短信。**MVP 实际仅实现「站内待办」**;邮件和企微为配置项占位,实际发送通道依赖独立基建,列 Mid 增强 |
| 事件订阅表 | 事件类型、通知方式、订阅角色、静默规则 |
| 操作 | "保存配置"按钮 |
**事件类型订阅清单**(与 Callback 事件类型对齐):
| 事件类型 | 默认通道 | 默认角色 | 聚合规则 |
|---------|---------|---------|---------|
| Callback 待处理 | 站内 + 邮件 | LICENSE_OPS | 30 分钟内合并 |
| SN 待发放 | 站内 | LICENSE_OPS | 每日汇总 |
| 激活超期 7 日 | 站内 + 邮件 | DELIVERY + LICENSE_OPS | 不重复 |
| 换机申请待审批 | 站内 | LICENSE_OPS | 不重复 |
| 合同到期提醒 | 邮件 | SALES | 每周汇总 |
### 4.3 API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/todos` | 待办列表(支持 type, status, role 筛选) |
| PATCH | `/api/v1/todos/{id}/status` | 更新待办状态(认领/完成/忽略) |
| POST | `/api/v1/todos/batch-status` | 批量更新待办状态 |
| GET | `/api/v1/notifications/config` | 获取通知配置 |
| PUT | `/api/v1/notifications/config` | 更新通知配置 |
---
## 5. M9 报表与对账
### 5.1 数据说明
M9 **不新增独立表**,全部基于已有业务表做聚合查询:
| 视图 | 数据源 | 聚合逻辑 |
|------|--------|---------|
| 合同 vs SN 对账 | `platform_contract` + `platform_contract_line` + `platform_license_sn` | 按合同行分组 COUNT SN |
| 已发 vs 已激活 | `platform_license_sn` | 按 status 分组统计 |
| Callback 统计 | `platform_callback_inbox` | 按 event_type / status / 时间聚合 |
| 项目健康度 | 多表联合 | 交付率 / SN 发放率 / 激活率 加权计算 |
### 5.2 页面规格
**P1. 履约对账(/reports/contract-sn**
| 组件 | 说明 |
|------|------|
| 筛选栏 | 项目下拉、合同下拉、「查询」「导出 CSV」 |
| KPI 卡片 | 合同总行项数、已发 SN 数、未发缺口、已激活/已发 |
| 表格列 | 合同编号、客户、行项、应发、实发、已激活、缺口、状态(正常/缺额/超发) |
**P2. Callback 统计(/reports/callback-stats**
| 区块 | 内容 |
|------|------|
| 时间段选择 | 近 24 小时 / 近 7 天 / 近 30 天 / 自定义 |
| 事件类型分布 | 柱状图或百分比条:各事件类型占比 |
| 处理成功率趋势 | 日/周/月成功率 + 总量 |
| Top 失败原因 | 排名列表(失败原因 + 占比 + 趋势) |
**P3. 项目健康度看板(/reports/project-health**
| 组件 | 说明 |
|------|------|
| 表格列 | 项目名称、交付完成率、SN 发放率、激活率、健康度(🟢正常/🟡关注/🔴风险) |
| 健康度规则 | 绿:三项均 ≥80%;黄:任一项 <80%;红:任一项 <50% |
### 5.3 API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/reports/contract-sn` | 履约对账报表(支持 projectId, contractId 筛选) |
| GET | `/api/v1/reports/callback-stats` | Callback 统计(支持时间范围、eventType 筛选) |
| GET | `/api/v1/reports/project-health` | 项目健康度列表 |
| GET | `/api/v1/reports/export` | 导出 CSV — 后端生成文件流,前端触发下载(Content-Disposition: attachment |
---
## 6. 实现注意事项
### 6.1 与现有系统的集成
- **设备↔SN 绑定**`platform_device_sn_binding.license_sn_id` → 引用已有 `platform_license_sn.id`
- **待办自动生成**`platform_todo_item``source_id` + `source_type` 可指向 `platform_callback_inbox.id` 等已有业务表
- **报表聚合**:复用现有 MyBatis-Plus Mapper,新增报表专用的 `@Select` 查询方法
### 6.2 角色与权限
- 初始角色配置使用简化三角色(SYS_ADMIN / DEVELOPER / OPS
- Mid 阶段实际交付时应按 PRD §13.2 的产品定义角色集落地:
- DELIVERY → M7 设备读写
- LICENSE_OPS → M7 设备读写、M8 待办、M9 Callback 统计
- SALES → M9 合同对账只读
- FINANCE_VIEW / COMPLIANCE → M9 报表只读
- EXEC_VIEW → M9 项目健康度只读
### 6.3 M7-F05 设备事件联动实现机制
Callback `device:*` 事件(`device:pre_activate` / `post_activate`)到达时:
1. Webhook Ingress 接收并写入 `platform_callback_inbox`
2. `CallbackInboxService` 解析事件中的 `mid` 字段
3.`mid``platform_device` 中已存在 → 更新 `last_heartbeat_at`,并在 `platform_device_sn_binding` 添加记录
4.`mid` 不存在 → 自动创建设备草稿记录(status=INACTIVE),并在待办中心生成一条「新设备待确认」待办
5. Ops 可在设备详情页手动补充别名/场站/客户关联
此机制**不依赖 M7 管理页面存在**即可在后台运行;M7 页面提供的是查看和手工干预入口。
### 6.4 与 PRD 状态对照
| 功能点 | 本设计覆盖 | 说明 |
|--------|-----------|------|
| M7-F01 设备登记 | ✅ | 登记对话框 + 列表 |
| M7-F02 绑定历史 | ✅ | 设备详情时间线 |
| M7-F03 换机申请 | ✅ | 换机申请对话框 + 审批 |
| M7-F04 设备检索 | ✅ | 列表多维度筛选 |
| M7-F05 Callback 联动 | ✅ | 待办自动生成 |
| M7-F06 策略展示 | ❌ 后续 | 依赖 BitAnswer 策略查询 |
| M8-F01 待办列表 | ✅ | 待办中心 |
| M8-F02 认领/完成 | ✅ | 状态 PATCH + 批量 |
| M8-F03 通知通道 | ✅ | 通知配置页 |
| M8-F04 通知模板 | ✅ | 事件→模板配置 |
| M8-F05 静默规则 | ✅ | 聚合规则配置 |
| M9-F01 合同 vs SN | ✅ | 履约对账页 |
| M9-F02 已发 vs 激活 | ✅ | 履约对账页含 |
| M9-F03 Callback 统计 | ✅ | Callback 统计页 |
| M9-F04 导出 CSV | ✅ | 导出按钮 |
| M9-F05 项目健康度 | ✅ | 健康度看板 |
| M9-F06 订阅报表 | ❌ 后续 | 定时邮件推送 |
---
## 7. 页面关系图
```mermaid
flowchart TB
subgraph Existing["已有页面(I1-I9"]
Login[登录页]
Home[首页]
Customers[客户管理]
Projects[项目]
Contracts[合同管理]
Deliveries[交付管理]
SNS[许可 SN]
Callbacks[Callback 收件箱]
Integration[集成配置]
Audit[审计日志]
end
subgraph New["新增页面(Mid"]
Devices[设备列表]
DeviceDetail[设备详情]
SwapRequest[换机申请]
Todos[待办中心]
NotifConfig[通知设置]
ReportCS[履约对账]
ReportCB[Callback 统计]
ReportPH[项目健康度]
end
Login --> Home
Home --> Customers --> Projects
Projects --> Contracts
Contracts --> Deliveries
Deliveries --> SNS
Callbacks --> SNS
Integration --> SNS
SNS --> Devices
Devices --> DeviceDetail
DeviceDetail --> SwapRequest
Callbacks --> Todos
SNS --> Todos
Devices --> Todos
Contracts --> ReportCS
SNS --> ReportCS
Callbacks --> ReportCB
ReportCS --> ReportPH
ReportCB --> ReportPH
```
---
## 8. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-05-25 | 初版:基于 PRD 更新版和 Visual Companion 线框图确认结果撰写 |
@@ -0,0 +1,318 @@
# 代码实现审计报告 — PRD vs 实际实现
**审计日期:** 2026-05-26
**审计范围:**
- PRD 文档: `chuangfei-platform-product-modules.md` (M1-M11), `FRONTEND_UI_SPECIFICATION.md`, `chuangfei-platform-bpm-and-roadmap.md`
- 后端: `services/delivery-platform-api/` (153 Java 文件) + `services/license-webhook-ingress/`
- 前端: `web/delivery-platform-ui/src/` (47 源文件)
---
## 1. 严重缺陷 (Critical)
### CR-01: 认证系统硬编码用户凭据
**位置:** `AuthController.java:54-89`
**PRD 对照:** M11-F14 要求「用户与账号生命周期:创建、启用/禁用、离职归档」
**实际实现:** 4 个用户硬编码在 Java switch 语句中:
```java
case "admin" role=SYS_ADMIN
case "sales" role=SALES
case "delivery" role=DELIVERY
case "ops" role=LICENSE_OPS
```
**缺陷:**
- 密码 = 小写用户名 (`pass.equals(user.toLowerCase())`) — admin/admin, sales/sales
- 无数据库用户表 — 无法 CRUD、禁用、归档
- 注入的 `PasswordEncoder` (BCrypt) 仅用于 `changePassword` 端点,登录完全不使用
- `changePassword` 验证旧密码时硬编码 `passwordEncoder.encode("admin")`,对非 admin 用户永远失败
**影响:** 无法管理用户、密码与用户名相同、安全基线不达标
---
### CR-02: JWT Token 存储在 localStorage
**位置:** `stores/auth.js:4,8,29,37`
**PRD 对照:** §16.6 已知局限明确标注「前端 Token 存 localStorage(非 HttpOnly Cookie)」为已知安全缺陷,计划 Mid 迁移
**实际实现:** Token 通过 `localStorage.setItem(TOKEN_KEY)` 持久化
```javascript
// auth.js:29
localStorage.setItem(TOKEN_KEY, this.token);
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`;
```
**缺陷:** XSS 攻击可窃取 localStorage 中的 JWT,获得完整 API 访问权限
**影响:** 安全 — XSS 窃取 → 权限丢失
**缓解:** 当前通过 CSP + 前端无富文本渲染降低风险,但未根本解决
---
### CR-03: Error Message 泄露
**位置:**
- `LicenseController.java:25``ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()))`
- `ContractController.java:136``ResponseEntity.status(500).body(Map.of("error", e.getMessage()))`
**PRD 对照:** 无明确的错误消息规范,但全局 `ApiExceptionHandler` 已返回泛化消息 "服务器内部错误"
**缺陷:** 这两个 Controller 使用 try-catch 捕获 `Exception` 并将异常消息原文 (`e.getMessage()`) 返回给客户端,绕过了全局异常处理器。可能泄露实现细节(表名、SQL、文件路径)。
**影响:** 信息安全 — 生产环境可能泄露堆栈或内部路径信息
---
### CR-04: resetPassword 和 forceLogout 是空操作
**位置:** `AuthController.java:131-148`
**PRD 对照:** M11-F08「密码重置: 管理员重置密码或邮件/短信重置链接」、M11-F12「管理员强制下线」
**实际实现:** 两个端点均仅有参数校验,无实际逻辑
```java
// resetPassword — 校验参数后直接返回 200 OK,未更新任何密码
@PostMapping("/admin/reset-password")
public ResponseEntity<Void> resetPassword(@RequestBody Map<String, String> body) {
String username = body.get("username");
String newPassword = body.get("newPassword");
if (username == null || newPassword == null || newPassword.length() < 6) {
throw new ResponseStatusException(...);
}
return ResponseEntity.ok().build(); // 没有实际更新密码!
}
// forceLogout — 同上,无会话失效逻辑
@PostMapping("/admin/force-logout")
public ResponseEntity<Void> forceLogout(@RequestBody Map<String, String> body) {
String username = body.get("username");
if (username == null) throw new ResponseStatusException(...);
return ResponseEntity.ok().build(); // 没有实际使会话失效!
}
```
**影响:** 功能完全不可用 — 前端调用后显示成功,实际无效果
---
## 2. 高危缺陷 (High)
### HI-01: 无用户管理数据库表
**位置:** `AuthController.java` (全部)
**PRD 对照:** M11-F14「用户与账号生命周期:创建、启用/禁用、离职归档」— P0
**实际:**`platform_user` 表或类似实体。4 个用户硬编码。Flyway 迁移 V15 `seed_product_roles.sql` 仅涉及角色种子数据。
**影响:** M11-F14 完全未实现,无法添加/禁用/管理用户
---
### HI-02: 权限模型硬编码
**位置:** `AuthController.java:91-114`
**PRD 对照:** §13.4 要求权限码命名规范(如 `customer:project:rw``contract:order:export`
**实际:** 权限字符串在 Java switch 中硬编码,非数据库驱动、不可配置
```java
case "SALES":
permissions.add("customer:*");
permissions.add("project:*");
permissions.add("contract:*");
permissions.add("delivery:read");
break;
```
**影响:** 新增角色或调整权限需改代码重启;权限码 `v-permission` 指令在前端存在但后端无对应校验
---
### HI-03: 会话管理后端无状态
**位置:** `SecurityConfig.java:55-56``SessionCreationPolicy.STATELESS`
**PRD 对照:** M11-F03「空闲超时自动登出」、M11-F11「并发会话策略」
**实际:** JWT 无状态设计意味着后端无法主动使会话失效(无会话存储)。空闲超时仅在前端实现(`idleTimer.js`),后端无法强制登出。`forceLogout` API 为空操作。
**影响:** 并发会话、强制下线、空闲超时功能均无法在后端层面实现
---
### HI-04: LicenseController 异常处理绕过全局 Handler
**位置:** `LicenseController.java:19-27`
**PRD 对照:** 全局 `ApiExceptionHandler` 已提供统一错误格式 `{status, message}`
**实际:** `create` 方法手动 try-catch,返回非标准错误格式
```java
try {
return ResponseEntity.ok(licenseService.create(request));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
}
```
返回格式为 `{"error": "..."}` 而非全局标准的 `{"status": 500, "message": "..."}`
**影响:** API 响应格式不一致,前端 `apiErrorMessage.js` 可能无法解析
---
## 3. 中危缺陷 (Medium)
### ME-01: 合同附件上传无校验
**位置:** `ContractController.java:118-137`
**PRD 对照:** M2-F05「合同附件:上传扫描件/电子签输出(存储与权限受控)」
**实际:** 上传端点无文件大小限制、无文件类型白名单、无病毒扫描
```java
@PostMapping("/{id}/attachments")
public ResponseEntity<Map<String, Object>> uploadAttachment(@PathVariable Long id, @RequestParam("file") MultipartFile file) {
// 无 file.getSize() 校验
// 无 file.getContentType() 白名单
// 直接将文件写入本地磁盘
```
**影响:** 可能被用于上传恶意文件;磁盘可能被大文件填满
---
### ME-02: 部分 Controller 返回格式不统一
**位置:** 多文件
**PRD 对照:** 无明确 API 响应规范
**实际:** 存在三种返回风格:
1. 全局 `ApiExceptionHandler``{status, message}` (标准)
2. `LicenseController``{error, ...}` (非标准)
3. `ContractController``{error, ...}` (非标准)
4. 部分端点直接返回实体对象(非 Map)
**影响:** 前端 `apiErrorMessage.js` 兼容多种格式但无法覆盖所有情况
---
### ME-03: 系统参数仅存于 localStorage
**位置:** `SystemParamsView.vue:14-33`
**PRD 对照:** M11-F20「系统参数」— 期望持久化到后端数据库
**实际:**
```javascript
// 直接保存到浏览器 localStorage
localStorage.setItem('systemParams', JSON.stringify(params.value))
ElMessage.success('参数已保存(MVP: 存储于浏览器本地)')
```
**影响:** 参数仅对当前浏览器有效,不同用户/设备参数不一致;清除浏览器数据后丢失
---
### ME-04: 后端 changePassword 验证逻辑错误
**位置:** `AuthController.java:151-168`
**PRD 对照:** M11-F07「已登录用户修改本人密码;校验旧密码强度与新密码策略」
**实际:**
```java
String currentPasswordHash = passwordEncoder.encode("admin"); // 始终比较 admin 的密码!
if (!passwordEncoder.matches(oldPassword, currentPasswordHash)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
}
```
`passwordEncoder.encode("admin")` 硬编码为 "admin",导致:
- admin 用户可以改密(旧密码 = admin 通过)
- 其他用户(sales/delivery/ops)永远无法通过旧密码验证
---
### ME-05: SN 批量导入缺少事务回滚
**位置:** `LicenseSnService.java:134-155`
**PRD 对照:** M4-F01/F07 批量操作
**实际:** 逐条插入,失败项跳过继续,但方法未标注 `@Transactional`
```java
public Map<String, Object> batchImport(List<LicenseSnCreateRequest> requests) {
// for 循环逐条 insert,无事务保护
// 部分成功 = 部分写入无法回滚
```
**影响:** 批量导入 100 条中第 50 条失败时,前 49 条已写入无法撤销
---
## 4. PRD 与实现偏离 (Misalignment)
### MA-01: M11 角色模型偏离产品定义
| 产品定义角色 | 实际实现 | 状态 |
|------------|---------|------|
| SYS_ADMIN | ✅ | 产品定义包含 |
| SALES | ✅ I10 新增 | 产品定义包含 |
| DELIVERY | ✅ I10 新增 | 产品定义包含 |
| LICENSE_OPS | ✅ I10 新增 | 产品定义包含 |
| ORDER_SUPPORT | ○ | 产品定义但未实现 |
| FINANCE_VIEW | ○ | 产品定义但未实现 |
| COMPLIANCE | ○ | 产品定义但未实现 |
| EXEC_VIEW | ○ | 产品定义但未实现 |
| SECURITY_ADMIN | ○ | 产品定义但未实现 |
| DEVELOPER | ✅ (应废弃) | MVP 遗留非标角色 |
| OPS | ✅ (应废弃) | MVP 遗留非标角色 |
前端路由角色标记(`router/index.js`)仍广泛使用 `SYS_ADMIN``SALES`,但 `DEVELOPER` 已从路由角色列表中移除,而 `LICENSE_OPS``DELIVERY` 已加入。
### MA-02: M1-F07 客户冻结后端就绪前端缺 UI
**位置:** 后端 `CustomerController.java``PATCH /{id}/freeze``/unfreeze` 端点,前端 `CustomersView.vue` 无冻结操作入口
### MA-03: M11-F07 密码修改已实现但产品文档未标记
`ProfileView.vue` 已包含完整的改密弹窗,`AuthController` 有对应端点,但文档标注为 ○。
---
## 5. 代码质量问题 (Code Quality)
### CQ-01: SecurityConfig 重复 import (已修复)
**位置:** `SecurityConfig.java:5,20` — 两次 `import org.springframework.context.annotation.Bean`
**状态:** ✅ 本次审计已修复
### CQ-02: 个别 Controller 使用 `@PreAuthorize` 而非 JWT Filter 角色
**位置:** `LicenseController.java:20,30,40` — 使用 `@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")`
**问题:** 与 JWT Filter 的双重验证增加了 role 前缀处理的复杂性(JwtAuthFilter 添加 `ROLE_` 前缀,`@PreAuthorize` 期望 `ROLE_` 格式)
### CQ-03: 前端 API 层中个别函数含 query 参数拼接
**位置:** `platform.js:460`
```javascript
export function createSkuMapping(contractLineId, body) {
return axios.post(`/api/v1/integration/sku-mappings?contractLineId=${contractLineId}`, body);
}
```
**影响:** 非紧急,但建议统一使用 `{ params: { contractLineId } }` 方式
---
## 6. 未被 PRD 覆盖但代码已实现的模块(超前实现)
| 模块 | 功能 | 建议 |
|------|------|------|
| M7 设备管理 | 登记/列表/详情/绑定/换机申请 | 核对 PRD 需求后决定是否纳入正式范围 |
| M8 通知待办 | 待办中心 + 通知通道配置 UI | 需补充实际发送逻辑 |
| M9 报表对账 | 4 个报表页面均已上线 | 补充导出按钮和推送逻辑 |
| M6 ID/特征/SKU 映射 | 前后端均已实现 | 更新产品文档状态 |
---
## 7. 汇总统计
| 严重级别 | 数量 | 编号 |
|---------|------|------|
| 🔴 Critical | 4 | CR-01~CR-04 |
| 🟠 High | 4 | HI-01~HI-04 |
| 🟡 Medium | 5 | ME-01~ME-05 |
| 🔵 Misalignment | 3 | MA-01~MA-03 |
| ⚪ Code Quality | 3 | CQ-01~CQ-03 |
| **合计** | **19** | |
---
## 8. 修复建议优先级
### P0 — 立即修复(安全基线)
1. **CR-01** (硬编码用户): 创建 `platform_user` 表 + Flyway 迁移 + AuthController 改用数据库查询 + BCrypt 密码校验
2. **CR-04** (空操作端点): `resetPassword``forceLogout` 补充实际逻辑 — resetPassword 更新用户密码,forceLogout 增加黑名单机制
3. **CR-03** (错误泄露): `LicenseController``ContractController` 移除 try-catch,让全局 `ApiExceptionHandler` 接管
4. **ME-04** (改密逻辑错误): `changePassword` 从 SecurityContext 获取当前用户名,从数据库查询对应用户的密码哈希
### P1 — 短期修复
5. **HI-01** (用户管理): 在 P0 用户表基础上实现用户 CRUD API + 前端管理页面
6. **ME-01** (附件校验): ContractController upload 增加 `@Size` 注解和文件类型白名单
7. **ME-05** (事务缺失): `batchImport` 方法添加 `@Transactional`
### P2 — 中长期
8. **CR-02** (Token 存储): 迁移至 HttpOnly Cookie(需后端配合返回 Set-Cookie header
9. **HI-02** (权限模型): 权限码持久化到数据库,实现可配置 RBAC
10. **HI-03** (会话管理): 引入 Token 黑名单/白名单机制或 Redis 会话存储
@@ -0,0 +1,469 @@
# 原型实现复盘 — 缺漏功能 & 页面清单
**生成日期:** 2026-05-26
**参考来源:**
- `docs/chuangfei-platform-product-modules.md` (§2~§12 功能点表 + §16 原型说明)
- `docs/engineering/FRONTEND_UI_SPECIFICATION.md` (前端 UI 规格)
- `services/delivery-platform-api/` 全部 Controller 端点
- `web/delivery-platform-ui/src/router/index.js` + `src/views/` (38 视图)
- `docs/engineering/iterations/I9_IMPLEMENTATION_REVIEW.md`
---
## 总览
| 模块 | 功能点总数 | ✅ 已实现 | ◐ 部分实现 | ○ 未实现 | — 依赖前置 |
|------|-----------|-----------|------------|----------|-----------|
| **M1** 客户与项目 | 9 | 4 | 1 | 4 | 0 |
| **M2** 合同与履约行 | 9 | 5 | 0 | 4 | 0 |
| **M3** 交付管理 | 8 | 5 | 1 | 2 | 0 |
| **M4** 授权与许可运营 | 11 | 3 | 2 | 5 | 1 |
| **M5** Callback 运营 | 10 | 7 | 1 | 2 | 0 |
| **M6** 授权集成与配置 | 9 | 3 | 0 | 6 | 0 |
| **M7** 设备与终端 | 6 | 0 | 4 | 2 | 0 |
| **M8** 通知与待办 | 5 | 0 | 2 | 3 | 0 |
| **M9** 报表与对账 | 6 | 0 | 4 | 2 | 0 |
| **M10** 审计与合规 | 4 | 1 | 1 | 2 | 0 |
| **M11** 身份与平台管理 | 21 | 6 | 3 | 12 | 0 |
| **合计** | **98** | **34 (35%)** | **19 (19%)** | **44 (45%)** | **1 (1%)** |
> **说明**: 实际代码实现程度高于产品模块文档中的状态标记。以下按模块逐个详细盘点。
---
## M1 — 客户与项目中心
**当前页面**: `/customers` `CustomersView.vue`, `/customers/:id` `CustomerDetailView.vue`, `/projects` `ProjectsView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M1-F01 | 客户档案创建/编辑 | ◐ | 仅 name + credit_code,缺行业/地址/开票信息字段 |
| M1-F02 | 客户列表与检索 | ✅ | 关键词搜索 + 分页 |
| M1-F03 | 客户详情聚合视图 | ○ | **未实现** — 缺少关联项目数/在履约合同/在途 SN 统计摘要。后端 `GET /{id}/summary` 已存在 |
| M1-F04 | 项目创建/编辑 | ◐ | 仅 name + customer_id + phase,缺计划起止日期、项目经理 |
| M1-F05 | 项目列表与筛选 | ✅ | 按客户、阶段筛选 |
| M1-F06 | 项目干系人 | ◐ | 后端有 `/stakeholders` CRUD 端点,但前端 **无独立 UI 入口**(仅 API 可用) |
| M1-F07 | 客户/项目冻结与解冻 | ◐ | 后端 `PATCH /{id}/freeze` `/unfreeze` 已实现,但前端 **缺 UI 操作** |
| M1-F08 | 客户合并与去重 | ○ | 未开始 |
| M1-F09 | 外部 CRM 主数据同步 | ○ | 未开始 |
### 前端页面缺口
| 缺漏 | 说明 |
|------|------|
| 客户详情聚合视图 | 后端 `/customers/{id}/summary` 已就绪,缺前端展示页 |
| 项目干系人管理 UI | 后端 CRUD 就绪,前端口/弹窗未实现 |
| 冻结/解冻操作 UI | 后端端点就绪,前端缺按钮和确认弹窗 |
---
## M2 — 合同与履约行
**当前页面**: `/contracts` `ContractsView.vue`, `/contracts/new` `ContractWizardView.vue`, `/contracts/:id` `ContractDetailView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M2-F01 | 合同登记与编辑 | ✅ | 完整 CRUD + 客户/项目关联 |
| M2-F02 | 合同状态机 | ✅ | DRAFT→PENDING_EFFECTIVE→EFFECTIVE→CHANGING→TERMINATED |
| M2-F03 | 合同标的摘要 | ✅ | 行项汇总展示 |
| M2-F04 | 合同行项 | ✅ | 多行 CRUD,含数量/单位 |
| M2-F05 | 合同附件 | ◐ | 后端有 `POST /{id}/attachments` 端点,前端 **缺上传 UI** |
| M2-F06 | 合同与订单关联 | ○ | 未开始 |
| M2-F07 | 合同变更与版本 | ◐ | 后端有 `POST /{id}/changes` + `/complete` 端点,前端 **缺变更 UI** |
| M2-F08 | 合同行↔SKU 映射 | ○ | 依赖 M6 联动 |
| M2-F09 | 合同到期与续费提醒 | ○ | 依赖 M8 联动 |
### 前端页面缺口
| 缺漏 | 说明 |
|------|------|
| 附件上传弹窗 | 后端端点已存在,详情页缺附件区块 |
| 变更单发起/完成 UI | 后端 `changes` 端点已实现,合同详情缺变更操作入口 |
---
## M3 — 交付管理
**当前页面**: `/deliveries` `DeliveriesView.vue`, `/deliveries/new` `DeliveryBatchWizardView.vue`, `/deliveries/:id` `DeliveryBatchDetailView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M3-F01 | 交付批次创建 | ✅ | |
| M3-F02 | 交付清单 | ✅ | 行项管理 |
| M3-F03 | 交付与合同行关联 | ✅ | |
| M3-F04 | 交付状态 | ✅ | PENDING→DELIVERED→CANCELLED |
| M3-F05 | 交付完成确认 | ✅ | |
| M3-F06 | 现场环境信息 | ○ | 未实现 |
| M3-F07 | SN 发放门禁 | ○ | 后端 system params `deliveryGateEnabled` 已定义,但门禁逻辑未实际执行 |
| M3-F08 | 交付模板 | ○ | 未开始 |
---
## M4 — 授权与许可运营
**当前页面**: `/licenses/sn` `LicenseSnListView.vue`, `/licenses/sn/new` `LicenseSnWizardView.vue`, `/licenses/sn/:id` `LicenseSnDetailView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M4-F01 | SN 手工录入/导入 | ◐ | 手工录入✅,批量导入 **缺前端 UI**(后端 `POST /batch-import` 已存在) |
| M4-F02 | SN 与合同/项目/客户绑定 | ✅ | |
| M4-F03 | SN 生命周期状态 | ✅ | REGISTERED→ISSUED→ACTIVATED→SUSPENDED→REVOKED |
| M4-F04 | SN 详情页 | ✅ | 绑定/状态/备注 |
| M4-F05 | 激活结果回写 | ◐ | 支持手工状态更新,缺原因码分类 |
| M4-F06 | 比特控制台状态摘要 | ○ | 依赖比特对接,未实现 |
| M4-F07 | 批量 SN 操作 | ◐ | 后端 `POST /batch-import` 已存在,前端 **缺批量操作 UI** |
| M4-F08 | 授权需求单 | ○ | 未开始 |
| M4-F09 | 试用/正式/续期标签 | ○ | 未开始 |
| M4-F10 | SN 与设备关联视图 | — | 依赖 M7 |
| M4-F11 | 授权策略生效视图 | ○ | 依赖 M6 联动 |
### 前端页面缺口
| 缺漏 | 说明 |
|------|------|
| 批量导入 SN UI | 后端 `POST /license-sns/batch-import` 已就绪,前端缺导入页面/弹窗 |
| 批量 SN 操作 UI | 列表页缺批量选择 + 批量状态变更 |
| 原因码分类选择 | 详情页状态变更时缺原因码下拉 |
---
## M5 — Callback 运营
**当前页面**: `/callbacks` `CallbackInboxView.vue`, `/callbacks/:id` `CallbackInboxDetailView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M5-F01 | 事件收件箱列表 | ✅ | 多维度筛选 |
| M5-F02 | 事件详情 | ✅ | payload 脱敏预览 |
| M5-F03 | 处理状态 | ✅ | PENDING→PROCESSED/FAILED/IGNORED |
| M5-F04 | 关联解析失败兜底 | ✅ | 人工挂接 SN/项目/合同 |
| M5-F05 | 事件类型字典 | ✅ | |
| M5-F06 | 失败原因标注 | ○ | 未实现 |
| M5-F07 | 批量重处理/重试 | ◐ | 单条 DEAD 重放✅ (I8)**批量未做** |
| M5-F08 | 死信与积压监控视图 | ○ | 未实现 |
| M5-F09 | 事件驱动待办 | — | 依赖 M8 |
| M5-F10 | 模拟投递 | ◐ | `POST /simulate` 端点已存在,前端 **缺测试工具 UI** |
### 前端页面缺口
| 缺漏 | 说明 |
|------|------|
| 模拟投递测试工具 | 后端 `POST /callback-inbox/simulate` 就绪,前端缺页面/弹窗 |
| 批量重处理 UI | 列表页缺批量选择 + 批量重入队 |
| 死信积压监控 | 独立视图或仪表盘区块 |
---
## M6 — 授权集成与配置
**当前页面**: `/integration/environments`, `/integration/product-lines`, `/integration/id-mappings`, `/integration/sku-mappings`, `/integration/feature-mappings`, `/integration/json-templates`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M6-F01 | 产品线定义 | ✅ | 列表已实现 |
| M6-F02 | 环境维度 | ✅ | dev/prod seed 数据 |
| M6-F03 | 比特 ID 映射 | ◐ | 前端 `IntegrationIdMappingView` 已存在,后端 CRUD 就绪,但 **产品模块标记为○**,需确认映射字段对齐 |
| M6-F04 | 特征映射 | ◐ | 前端 `IntegrationFeatureMappingView` 已存在,后端就绪 |
| M6-F05 | JSON 模板管理 | ◐ | 前端 `IntegrationJsonTemplateView` 已存在,后端 CRUD 就绪,但 **Schema 校验未关联 UI** |
| M6-F06 | 配置发布记录 | ○ | 未实现 |
| M6-F07 | 控制台链接与说明 | ○ | 未实现 |
| M6-F08 | SDK 版本矩阵 | ○ | 未开始 |
| M6-F09 | 变更影响分析 | ○ | 未开始 |
> **说明**: M6 实际代码实现远超产品文档标记。ID 映射/特征映射/JSON 模板 的前后端均已实现但未标记。需核对字段完整性。
---
## M7 — 设备与终端治理
**当前页面**: `/devices` `DeviceListView.vue`, `/devices/:id` `DeviceDetailView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M7-F01 | 设备登记 | ◐ | 前端 `DeviceListView` 已实现列表+登记弹窗,后端 CRUD 就绪,但字段覆盖需确认 |
| M7-F02 | 设备与 SN 绑定历史 | ◐ | `DeviceDetailView` 已实现,绑定时间线需核对完整性 |
| M7-F03 | 换机申请与处理 | ◐ | 后端 `POST /{id}/swap-request` 已存在,审批流未实现 |
| M7-F04 | 设备列表与检索 | ✅ | 已实现 |
| M7-F05 | 与 Callback 设备事件联动 | ○ | 未实现 |
| M7-F06 | 终端数/并发策略展示 | ○ | 未开始 |
> **说明**: M7 实际实现远超产品文档标记的"全 ○"。设备登记、列表、详情、绑定历史已上线。待确认字段和审批流完整性。
---
## M8 — 通知与待办
**当前页面**: `/todos` `TodoCenterView.vue`, `/notifications/settings` `NotificationSettingsView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M8-F01 | 站内待办列表 | ◐ | `TodoCenterView` 已实现,支持类型筛选/优先级/状态,但 **自动化生成待办** 未接入 |
| M8-F02 | 待办认领与完成 | ◐ | 状态流转已实现,但标注/备注功能待补 |
| M8-F03 | 邮件/企微通道 | ◐ | `NotificationSettingsView` 已实现通道配置 UI + 事件订阅表,但 **实际发送逻辑** 未接入 |
| M8-F04 | 通知模板 | ○ | 未实现 |
| M8-F05 | 静默规则 | ○ | 未开始 |
> **说明**: M8 实际实现远超产品文档标记。待办中心+通知设置均已上线。核心缺口是自动化待办生成和通知发送通道的实际对接。
---
## M9 — 报表与对账
**当前页面**: `/reports/contract-sn` `ContractSnReportView.vue`, `/reports/callback-stats` `CallbackStatsView.vue`, `/reports/project-health` `ProjectHealthView.vue`, `/reports/subscriptions` `SubscriptionReportView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M9-F01 | 合同标的 vs 已发 SN 视图 | ◐ | `ContractSnReportView` 已实现,需核对数据准确性和维度 |
| M9-F02 | 已发 vs 已激活视图 | ○ | 未专门实现(可合并到 F01) |
| M9-F03 | Callback 统计 | ◐ | `CallbackStatsView` 已实现 |
| M9-F04 | 导出 CSV/Excel | ◐ | 后端 `GET /reports/export` 已存在,前端 **缺导出按钮 UI** |
| M9-F05 | 项目健康度看板 | ◐ | `ProjectHealthView` 已实现,红黄绿规则可配置性待确认 |
| M9-F06 | 订阅报表 | ◐ | `SubscriptionReportView` 已实现,后端推送逻辑待确认 |
> **说明**: M9 的实际实现远超文档标记,4 个报表页面均已上线。
---
## M10 — 审计与合规
**当前页面**: `/audit` `AuditSearchView.vue`, `/audit/retention` `AuditRetentionView.vue`
### 功能点复盘
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M10-F01 | 关键字段变更日志 | ✅ | |
| M10-F02 | 审计检索 | ◐ | `AuditSearchView` 已实现,筛选维度待确认是否齐全 |
| M10-F03 | 导出审计包 | ○ | 未实现 |
| M10-F04 | 留存策略配置 | ◐ | `AuditRetentionView` 已实现,配置生效待确认 |
---
## M11 — 身份、访问与平台管理
**当前页面**: `/login` `LoginView.vue`, `/profile` `ProfileView.vue`, `/admin/params` `SystemParamsView.vue`
**其他**: `/403`, `/404`
### 12.1 账户登录、登出与会话
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M11-F01 | 登录页 | ✅ | JWT Bearer |
| M11-F02 | 登出 | ✅ | |
| M11-F03 | 登录态保持与超时 | ○ | **未实现** — 空闲超时自动登出缺失。`sessionTimeoutMinutes` 系统参数已定义但前端路由守卫未接入 |
| M11-F04 | 未登录访问拦截 | ✅ | 路由 `requiresAuth` guard |
| M11-F05 | 登录失败锁定 | ○ | **未实现** — 连续失败锁定/验证码缺失 |
| M11-F06 | 登录/登出审计 | ✅ | |
| M11-F07 | 密码修改 | ◐ | 后端 `POST /auth/change-password` 已存在,前端 Profile 页 **缺改密 UI** |
| M11-F08 | 密码重置 | ◐ | 后端 `POST /auth/admin/reset-password` 已存在,前端 **缺管理员重置 UI** |
| M11-F09 | 企业 SSO / OIDC | ○ | 未开始 |
| M11-F10 | 双因素认证 MFA | ○ | 未开始 |
| M11-F11 | 并发会话策略 | ○ | 后端未实现 |
| M11-F12 | 管理员强制下线 | ◐ | 后端 `POST /auth/admin/force-logout` 已存在,前端 **缺管理 UI** |
| M11-F13 | 服务时间窗提示 | ○ | 未开始 |
### 12.2 用户、角色与权限配置
| ID | 功能点 | 实现状态 | 详情 |
|----|--------|---------|------|
| M11-F14 | 用户与账号生命周期 | ◐ | 种子用户已创建,缺完整的用户管理页面(CRUD+启用/禁用) |
| M11-F15 | 角色定义与分配 | ◐ | 三角色已落地,产品定义 10+ 角色待补齐 |
| M11-F16 | 功能权限 RBAC | ◐ | 路由级 RBAC ✅,按钮级权限码 `v-permission` 正在落地未全覆盖 |
| M11-F17 | 数据范围 | ○ | 未开始 |
| M11-F18 | 数据属主/团队 | ○ | 未开始 |
| M11-F19 | 业务字典 | ✅ | |
| M11-F20 | 系统参数 | ◐ | `SystemParamsView` 已实现 + 后端 `system_params` 表,参数种类待扩充 |
| M11-F21 | 管理员敏感操作留痕 | ○ | 未实现 |
### 前端页面缺口
| 缺漏 | 说明 |
|------|------|
| 用户管理页面 | 用户 CRUD + 启用/禁用/角色分配页面缺失 |
| 角色管理页面 | 角色定义 + 权限码分配页面缺失 |
| 改密 UI | Profile 页缺修改密码表单 |
| 管理员重置密码 UI | 后端端点已存在,缺对应管理页面/弹窗 |
| 强制下线管理 UI | 后端端点已存在,缺在线会话管理页面 |
| 登录态超时拦截 | 系统参数已定义但前端路由守卫未接入空闲检测 |
---
## 前端页面完整盘点
### 已实现页面(38 视图)
| 路由 | 视图 | 模块 | 状态 |
|------|------|------|------|
| `/login` | `LoginView.vue` | M11 | ✅ |
| `/` | `HomeView.vue` | — | ✅ |
| `/customers` | `CustomersView.vue` | M1 | ✅ |
| `/customers/:id` | `CustomerDetailView.vue` | M1 | ✅ |
| `/projects` | `ProjectsView.vue` | M1 | ✅ |
| `/contracts` | `ContractsView.vue` | M2 | ✅ |
| `/contracts/new` | `ContractWizardView.vue` | M2 | ✅ |
| `/contracts/:id` | `ContractDetailView.vue` | M2 | ✅ |
| `/deliveries` | `DeliveriesView.vue` | M3 | ✅ |
| `/deliveries/new` | `DeliveryBatchWizardView.vue` | M3 | ✅ |
| `/deliveries/:id` | `DeliveryBatchDetailView.vue` | M3 | ✅ |
| `/licenses/sn` | `LicenseSnListView.vue` | M4 | ✅ |
| `/licenses/sn/new` | `LicenseSnWizardView.vue` | M4 | ✅ |
| `/licenses/sn/:id` | `LicenseSnDetailView.vue` | M4 | ✅ |
| `/licenses` | `LicenseList.vue` | V6 | ✅ |
| `/callbacks` | `CallbackInboxView.vue` | M5 | ✅ |
| `/callbacks/:id` | `CallbackInboxDetailView.vue` | M5 | ✅ |
| `/integration/environments` | `IntegrationEnvironmentsView.vue` | M6 | ✅ |
| `/integration/product-lines` | `IntegrationProductLinesView.vue` | M6 | ✅ |
| `/integration/id-mappings` | `IntegrationIdMappingView.vue` | M6 | ✅ |
| `/integration/sku-mappings` | `IntegrationSkuMappingView.vue` | M6 | ✅ |
| `/integration/feature-mappings` | `IntegrationFeatureMappingView.vue` | M6 | ✅ |
| `/integration/json-templates` | `IntegrationJsonTemplateView.vue` | M6 | ✅ |
| `/devices` | `DeviceListView.vue` | M7 | ✅ |
| `/devices/:id` | `DeviceDetailView.vue` | M7 | ✅ |
| `/todos` | `TodoCenterView.vue` | M8 | ✅ |
| `/notifications/settings` | `NotificationSettingsView.vue` | M8 | ✅ |
| `/reports/contract-sn` | `ContractSnReportView.vue` | M9 | ✅ |
| `/reports/callback-stats` | `CallbackStatsView.vue` | M9 | ✅ |
| `/reports/project-health` | `ProjectHealthView.vue` | M9 | ✅ |
| `/reports/subscriptions` | `SubscriptionReportView.vue` | M9 | ✅ |
| `/audit` | `AuditSearchView.vue` | M10 | ✅ |
| `/audit/retention` | `AuditRetentionView.vue` | M10 | ✅ |
| `/admin/params` | `SystemParamsView.vue` | M11 | ✅ |
| `/profile` | `ProfileView.vue` | M11 | ✅ |
| `/license-compare` | `LayoutCompareView.vue` | — | ✅ |
| `/403` | `ForbiddenView.vue` | M11 | ✅ |
| `/*` | `NotFoundView.vue` | M11 | ✅ |
### 结论:前端页面完整性
- **原型 UI 规格(§16.2)定义页面**: 全部 13 个页面已实现 ✅
- **超出原型范围的已实现页面(I10 及以上提前完成)**: 25 个额外页面(设备/待办/通知/报表/审计/集成配置等)
- **仍有缺口的功能区域**(见各模块复盘)
---
## 后端 API 缺口汇总
以下端点已在产品模块文档中定义但尚未实现:
| 模块 | 缺失端点 | 说明 |
|------|---------|------|
| M1 | 客户详情聚合统计 | `/customers/{id}/summary` 已存在,确认数据完整性 |
| M1 | 客户合并 | 无端点 |
| M2 | 订单关联 | 无端点 |
| M4 | 批量 SN 导入 | `POST /batch-import` 已存在,前端缺 UI |
| M5 | 批量重处理 | 无端点 |
| M6 | 配置发布记录 | 无端点 |
| M6 | 版本矩阵 | 无端点 |
| M7 | 换机审批流程 | 无审批端点 |
| M8 | 通知通道实际发送 | 配置已就绪,发送接口未接入 |
| M9 | 报表导出 | `GET /reports/export` 已存在,前端导出按钮缺失 |
| M10 | 审计导出包 | 无端点 |
| M11 | 用户管理 CRUD | 无专用端点(当前硬编码种子用户) |
| M11 | 角色管理 CRUD | 无专用端点 |
| M11 | 在线会话管理 | `force-logout` 已存在,会话列表端点缺失 |
---
## 按优先级分类的缺失功能清单
### P0 级别(MVP 应含但未完成)
| 功能 | 模块 | 说明 |
|------|------|------|
| 客户详情聚合视图 (M1-F03) | M1 | 后端 `/summary` 已就绪,前端未开发 |
| 登录态空闲超时 (M11-F03) | M11 | 安全基线必备,`sessionTimeoutMinutes` 参数已定义但未接入 |
| 登录失败锁定 (M11-F05) | M11 | 安全基线必备 |
| 密码修改 (M11-F07) | M11 | 后端端点已存在,Profile 页缺 UI |
| 用户管理页面 (M11-F14) | M11 | 用户 CRUD + 启用/禁用页面缺失 |
### P1 级别(增强运营效率)
| 功能 | 模块 | 说明 |
|------|------|------|
| 项目干系人管理 UI (M1-F06) | M1 | 后端就绪前端口 |
| 客户/项目冻结 UI (M1-F07) | M1 | 后端就绪前端口 |
| 合同附件管理 UI (M2-F05) | M2 | 后端就绪前端口 |
| 合同变更 UI (M2-F07) | M2 | 后端 `changes` 端点就绪前端口 |
| 批量 SN 导入 (M4-F01/F07) | M4 | 后端就绪前端口 |
| Callback 模拟投递 UI (M5-F10) | M5 | 后端就绪前端口 |
| Callback 批量重处理 (M5-F07) | M5 | 后端缺批量端点 |
| 账号密码重置 UI (M11-F08) | M11 | 后端就绪前端口 |
| 管理员强制下线 UI (M11-F12) | M11 | 后端就绪前端口 |
| 角色权限管理页面 (M11-F15/F16) | M11 | 角色 CRUD + 权限码分配 |
| 报表导出按钮 (M9-F04) | M9 | 后端就绪前端口 |
| 通知通道实际发送 (M8-F03) | M8 | 配置就绪但未对接 |
### P2 级别(治理与规模化)
| 功能 | 模块 |
|------|------|
| 客户合并与去重 (M1-F08) | M1 |
| CRM 同步 (M1-F09) | M1 |
| 合同到期续费提醒 (M2-F09) | M2 |
| 现场环境信息 (M3-F06) | M3 |
| 交付模板 (M3-F08) | M3 |
| 配置发布记录 (M6-F06) | M6 |
| SDK 版本矩阵 (M6-F08) | M6 |
| 变更影响分析 (M6-F09) | M6 |
| 终端并发策略展示 (M7-F06) | M7 |
| 通知模板 (M8-F04) | M8 |
| 静默规则 (M8-F05) | M8 |
| 审计导出包 (M10-F03) | M10 |
| SSO/OIDC (M11-F09) | M11 |
| MFA (M11-F10) | M11 |
| 数据范围 (M11-F17) | M11 |
| 数据属主 (M11-F18) | M11 |
---
## 与产品模块文档的状态差异(代码领先文档)
以下功能点的实际实现状态优于 `chuangfei-platform-product-modules.md` 中的标记:
| 功能点 | 文档标记 | 实际代码状态 | 差异说明 |
|--------|---------|-------------|---------|
| **M1-F06** 项目干系人 | ○ | ◐ | 后端 CRUD 已实现,仅前端口 |
| **M1-F07** 冻结解冻 | ○ | ◐ | 后端端点已实现,仅前端口 |
| **M2-F05** 合同附件 | ○ | ◐ | 后端上传端点已实现 |
| **M2-F07** 合同变更 | ○ | ◐ | 后端 `changes` 端点已实现 |
| **M4-F01** 批量导入 | ○ | ◐ | 后端 `batch-import` 已实现 |
| **M6** 全模块 | 大段 ○ | ◐ | ID 映射/JSON 模板/特征映射/SKU 映射均已前后端实现 |
| **M7** 全模块 | 全 ○ | ◐ | 设备登记/列表/详情/绑定历史已上线 |
| **M8** 全模块 | 全 ○ | ◐ | 待办中心+通知设置已上线 |
| **M9** 全模块 | 全 ○ | ◐ | 4 个报表页面均上线 |
| **M10-F02** 审计检索 | ○ | ◐ | 已实现 |
| **M10-F04** 留存策略 | ○ | ◐ | 已实现 |
---
## 修订建议
### 文档层面
1. **更新 `chuangfei-platform-product-modules.md`** — 大量功能点(M6/M7/M8/M9)实际代码已实现但文档标记为 ○,需批量刷新状态列
2. **更新 `FRONTEND_UI_SPECIFICATION.md`** — 新增页面(集成 ID 映射/SKU 映射/特征映射/JSON 模板/审计留存/待办中心/通知设置等)未纳入 UI 规格文档
3. **补充角色与实际菜单对照** — 当前角色定义(SYS_ADMIN/SALES/LICENSE_OPS/DELIVERY)与产品文档三角色不符,新增角色需更新路由权限
### 工程层面
1. **优先级确认** — 按 Mid 版本规划,聚焦 P0 安全基线缺口(M11-F03 超时/F05 锁定/F07 改密/F14 用户管理)+ P1 就绪后端触点(M1/M2/M4/M5/M11 前端 UI 补全)
2. **文档与代码对齐** — 先产出更新后的产品模块文档,再进入 I10 实现
---
**附录**: 本清单基于源码走查 + 产品文档对比生成,未运行端到端测试验证。建议在启动 I10 实现前,由 QA 逐个功能点做冒烟验证。
@@ -0,0 +1,265 @@
# 前端 UI 走查复盘报告
**日期:** 2026-05-27
**前端:** `web/delivery-platform-ui` — 38 views + MainLayout + Router
---
## 1. 侧栏菜单布局与排序
### 当前排序
```
📊 首页
👥 客户管理
📋 合同管理
📦 交付管理
🔑 许可 SN
🛡️ 许可证管理 [NEW]
📨 Callback 收件箱
🌐 集成环境
📱 产品线
🖥️ 设备管理
🔔 待办中心
📊 报表中心
📧 报表订阅
👤 用户管理
```
### 评估
| 方面 | 评价 |
|------|------|
| **分组** | 无分组。所有菜单项单层平铺,缺少二级分类。当菜单项增多时不易查找 |
| **排序** | 基本合理:核心业务(客户→合同→交付→SN)在前,运营支持(Callback→设备→待办)在中,管理类(报表→用户)在后 |
| **建议优化** | 可增加分组标签「业务管理」「运营管理」「系统管理」来归类 |
---
## 2. 首页(HomeView.vue)命名
### 当前状态
| 位置 | 显示文本 | 问题 |
|------|---------|------|
| 浏览器 title | 未设置 | `<title>` 标签缺失 |
| 登录页标题 | `客户商务与交付管理平台(I1` | I1 标签过时,应该是已迭代到 I10+ |
| 顶栏 nav | `授权平台` | 品牌简称,可接受 |
| 首页内容 | `首页` | 正确 |
| 首页 alert | `交付平台(I7` | 同样过时 |
### 问题
1. **无 `<title>` 标签** — 浏览器标签页显示 URL 路径而非平台名称
2. **I1 / I7 标签过时** — 登录页和首页仍显示迭代编号,应替换为稳定名称
3. **演示账号提示过时** — 登录页提示 `dev / devDEVELOPER` 但 DEVELOPER 角色已废弃,应改为 `sales / sales`
4. **首页内容标题与侧栏** — 顶部显示 `授权平台`,登录页显示 `客户商务与交付管理平台`,二者不一致
---
## 3. 逐页功能盘点
### M1 客户管理 (`/customers`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 客户列表 | ✅ | 名称/信用代码分页 |
| 搜索 | ✅ | 关键词搜索 |
| 新建/编辑弹窗 | ✅ | 含行业/地址/开票信息 |
| 冻结按钮 | ✅ | 后端就绪,前端已联动 |
| 删除(软删) | ✅ | |
| **详情聚合视图** | ✅ | 关联项目/合同/SN 统计 |
| **合并/去重** | ○ | Full 版本范围 |
### M1 项目管理 (`/projects`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 项目列表 | ✅ | |
| 按客户筛选 | ✅ | |
| 新建/编辑弹窗 | ✅ | |
| **干系人管理** | ✅ | CRUD 弹窗已实现 |
| 计划起止日期 | ✅ | 字段已存在 |
| 项目经理 | ✅ | 字段已存在 |
### M2 合同管理 (`/contracts`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 合同列表 | ✅ | |
| 三步创建向导 | ✅ | |
| 状态机操作 | ✅ | DRAFT→PENDING→EFFECTIVE→CHANGING→TERMINATED |
| 行项管理 | ✅ | |
| **附件上传** | ✅ | el-upload + 文件列表 |
| **合同变更** | ✅ | changes 端点已联动 |
| SKU 映射 | ○ | 未在前端展示 |
### M3 交付管理 (`/deliveries`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 交付列表 | ✅ | |
| 新建向导 | ✅ | |
| 状态变更 | ✅ | PENDING→DELIVERED→CANCELLED |
| 行项管理 | ✅ | |
| **现场环境信息** | ✅ | 部署地址/联系人/电话 |
| SN 发放门禁 | ○ | 后端参数已定义,逻辑未执行 |
### M4 许可 SN (`/licenses/sn`)
| 功能 | 状态 | 备注 |
|------|------|------|
| SN 列表 | ✅ | |
| 搜索 | ✅ | SN 编码/项目筛选 |
| **批量导入** | ✅ | CSV 批量导入弹窗 |
| **批量操作** | ✅ | 批量状态变更弹窗 |
| 详情页 | ✅ | 绑定/状态/备注 |
| 自研许可证管理 | ✅ | `/licenses` 额外页面 |
### M5 Callback (`/callbacks`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 事件收件箱 | ✅ | |
| 多维度筛选 | ✅ | 状态/事件类型/SN/项目/时间 |
| **批量重试按钮** | ✅ | 前端已实现,后端 `/batch-replay` 已新增 |
| **模拟投递弹窗** | ✅ | 已实现 |
| 详情页 | ✅ | payload 脱敏预览 |
| **失败原因下拉** | ✅ | 选择 FAILED 时弹出原因选择,后端已联通 |
| 状态处置 | ✅ | PENDING→PROCESSED/FAILED/IGNORED |
| 人工挂接 | ✅ | SN/项目/合同 |
| 死信积压 | ◐ | **后端端点已新增, 前端统计卡片需补充** |
### M6 集成配置 (`/integration/*`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 产品线 | ✅ | CRUD |
| 集成环境 | ✅ | CRUD |
| **ID 映射** | ✅ | 已实现 |
| **SKU 映射** | ✅ | 已实现 |
| **特征映射** | ✅ | 已实现 |
| **JSON 模板** | ✅ | CRUD |
### M7 设备管理 (`/devices`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 设备列表 | ✅ | MID/别名/站点/状态/心跳 |
| 设备登记弹窗 | ✅ | |
| 设备详情 | ✅ | 绑定历史 |
| 换机申请 | ◐ | 后端端点就绪,前端 UI 待补 |
### M8 待办中心 (`/todos`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 待办列表 | ✅ | 按类型/优先级/状态筛选 |
| 状态流转 | ✅ | |
| 通知配置 | ✅ | 通道/事件订阅 |
### M9 报表中心 (`/reports/*`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 合同 SN 报表 | ✅ | 含**导出 CSV 按钮** |
| Callback 统计 | ✅ | |
| 项目健康度 | ✅ | |
| 报表订阅 | ✅ | |
### M10 审计 (`/audit`)
| 功能 | 状态 | 备注 |
|------|------|------|
| 审计检索 | ✅ | |
| 留存策略 | ✅ | |
| 导出审计包 | ○ | 未实现(Full 版本) |
### M11 系统管理
| 页面 | 功能 | 状态 | 备注 |
|------|------|------|------|
| `/profile` | 个人设置/改密 | ✅ | |
| `/admin/params` | 系统参数 | ◐ | localStorage MVP |
| `/admin/users` | **用户管理** | ✅ | CRUD + 启用/禁用 |
| `/403` | 无权限 | ✅ | |
| `/*` | 404 | ✅ | |
---
## 4. 发现的问题清单
### 🔴 需要修复
| # | 问题 | 位置 | 建议 |
|---|------|------|------|
| 1 | 登录页演示提示仍显示 `dev/devDEVELOPER`DEVELOPER 已废弃 | `LoginView.vue:14` | 改为 `admin/admin123 / sales/sales / delivery/delivery / ops/ops` |
| 2 | 首页标题 `交付平台(I7` 过时 | `HomeView.vue` | 改为稳定名称,去掉迭代编号 |
| 3 | 登录页标题 `I1` 过时 | `LoginView.vue:4` | 去掉 `I1` |
| 4 | 无 `<title>` 标签 | `index.html` | 添加 `<title>创飞·交付管理平台</title>` |
| 5 | Callback 积压统计卡片未展示 | `CallbackInboxView.vue` | 列表上方增加统计条(pending/failed/最久未处理) |
### 🟡 建议优化
| # | 问题 | 位置 | 建议 |
|---|------|------|------|
| 6 | 侧栏菜单无分组 | `MainLayout.vue` | 增加「业务管理」「运营管理」「系统管理」分组标签 |
| 7 | `授权平台` vs `客户商务与交付管理平台` 名称不一致 | 全局 | 统一品牌名称 |
| 8 | 通知 badge 显示 `3` 为硬编码 | `MainLayout.vue:20` | 应从后端获取未读数 |
| 9 | `许可证管理` 标记 `NEW` 但已上线多时 | `MainLayout.vue:132` | 可去掉 NEW 标记 |
---
## 5. 页面功能完整度总表
| 页面 | 路由 | 完整度 | 备注 |
|------|------|--------|------|
| 登录 | `/login` | 95% | 演示提示需更新 |
| 首页 | `/` | 90% | 迭代编号过时 |
| 客户管理 | `/customers` | 95% | |
| 客户详情 | `/customers/:id` | 100% | 含聚合摘要 |
| 项目管理 | `/projects` | 100% | 含干系人 |
| 合同管理 | `/contracts` | 95% | |
| 合同向导 | `/contracts/new` | 100% | |
| 合同详情 | `/contracts/:id` | 100% | 含附件+变更 |
| 交付管理 | `/deliveries` | 95% | 含现场环境 |
| 交付向导 | `/deliveries/new` | 100% | |
| 交付详情 | `/deliveries/:id` | 100% | |
| 许可 SN | `/licenses/sn` | 100% | 含批量导入/操作 |
| SN 向导 | `/licenses/sn/new` | 100% | |
| SN 详情 | `/licenses/sn/:id` | 100% | |
| 许可证管理 | `/licenses` | 100% | 自研许可证 |
| Callback 收件箱 | `/callbacks` | 90% | 缺积压统计卡片 |
| Callback 详情 | `/callbacks/:id` | 100% | 含失败原因 |
| 集成环境 | `/integration/environments` | 100% | |
| 产品线 | `/integration/product-lines` | 100% | |
| ID 映射 | `/integration/id-mappings` | 100% | |
| SKU 映射 | `/integration/sku-mappings` | 100% | |
| 特征映射 | `/integration/feature-mappings` | 100% | |
| JSON 模板 | `/integration/json-templates` | 100% | |
| 设备管理 | `/devices` | 95% | 换机申请 UI 待补 |
| 设备详情 | `/devices/:id` | 100% | |
| 待办中心 | `/todos` | 100% | |
| 通知设置 | `/notifications/settings` | 100% | |
| 合同 SN 报表 | `/reports/contract-sn` | 100% | 含导出 |
| Callback 统计 | `/reports/callback-stats` | 100% | |
| 项目健康度 | `/reports/project-health` | 100% | |
| 报表订阅 | `/reports/subscriptions` | 100% | |
| 审计日志 | `/audit` | 100% | |
| 审计留存 | `/audit/retention` | 100% | |
| 系统参数 | `/admin/params` | 90% | localStorage 待迁移 |
| 用户管理 | `/admin/users` | 100% | |
| 个人设置 | `/profile` | 100% | |
| 403 | `/403` | 100% | |
| 404 | `/*` | 100% | |
---
## 6. 总结
- **总页面数**: 37 个页面/视图
- **100% 完成**: 30 页
- **90-95% 完成**: 6 页(需小修)
- **未实现**: 0 页
MVP/Mid 范围的前端 UI 已基本实现完毕。剩余工作集中在 Full 版本(积压监控卡片、换机审批流、审计导出包等)。
@@ -0,0 +1,217 @@
# Full 版本 (V2.0) 实现缺失与不足复盘
**日期:** 2026-05-27
**来源:** `docs/chuangfei-platform-product-modules.md` §13.5, §14, §16.7
---
## 1. Full 版本范围总览
Full 版本 = Mid + 所有 P2 功能点 + 以下专项能力:
| 分类 | 项目 | 涉及模块 | 当前状态 |
|------|------|---------|---------|
| **安全** | MFA 双因素认证 | M11-F10 | ○ 未开始 |
| **安全** | SECURITY_ADMIN 角色 | §13.2 | ○ 未开始 |
| **数据** | 事业部数据范围 (Data Scope) | M11-F17 | ○ 未开始 |
| **审计** | 审计导出包 | M10-F03 | ○ 未开始 |
| **集成** | CRM 同步 | M1-F09 | ○ 未开始 |
| **治理** | 细粒度互斥策略 | §13.5 | ○ 未开始 |
| **基础设施** | 消息队列 (MQ) | Webhook→API | ○ 未开始 |
| **基础设施** | 读模型分离 (CQRS) | 报表/查询 | ○ 未开始 |
| **P2 功能** | 13 个 P2 功能点(见 §2) | 多模块 | ◐ 部分上前端 |
---
## 2. P2 功能点逐项
| ID | 功能点 | 所属模块 | 当前状态 | Full 要求 |
|----|--------|---------|---------|----------|
| M1-F08 | 客户合并与去重 | M1 | ○ 未开始 | 疑似重复客户识别、合并流程与审计 |
| M1-F09 | CRM 主数据同步 | M1 | ○ 未开始 | 以外部 ID 关联、增量同步 |
| M2-F09 | 合同到期与续费提醒 | M2 | ○ 未开始 | 列表与订阅(与 M8 联动) |
| M3-F08 | 交付模板 | M3 | ○ 未开始 | 按产品线预置交付清单模板 |
| M4-F11 | 授权策略生效视图 | M4 | ○ 未开始 | 展示当前映射版本、环境(与 M6 联动) |
| M5-F10 | 模拟投递 | M5 | ◐ 后端就绪, 前端 UI 待补 | 联调验收工具 |
| M6-F08 | SDK/native 版本矩阵 | M6 | ○ 未开始 | 与现场客户端兼容范围说明 |
| M6-F09 | 变更影响分析 | M6 | ○ 未开始 | 映射变更影响哪些在服 SN/合同 |
| M7-F06 | 终端数/并发策略展示 | M7 | ○ 未开始 | 只读展示合同或比特策略摘要 |
| M8-F04 | 通知模板 | M8 | ○ 未开始 | 事件类型 → 模板变量 |
| M8-F05 | 静默规则 | M8 | ○ 未开始 | 重复事件聚合、防骚扰 |
| M9-F05 | 项目健康度看板 | M9 | ◐ 前端已上线 | 红黄绿规则可配置性待确认 |
| M9-F06 | 订阅报表 | M9 | ◐ 前端已上线 | 后端推送逻辑待确认 |
---
## 3. Full 版本专项能力详述
### 3.1 M11-F10 双因素认证 MFA
**PRD 要求:**
```
TOTP/短信/企业令牌等一种;可配置为全员或高敏角色必选
```
**当前缺口:**
- 无 TOTP 生成/验证逻辑
- 无短信网关集成
- 无 MFA 绑定/解绑 UI
- 无角色级 MFA 强制策略
**实现思路:** TOTP (Time-based One-Time Password) 方案,使用 `java.security` 或 Google Authenticator 兼容库,前端显示二维码 + 验证码输入。
**预估工作量:** 中(3-5 天,含 Flyway 迁移 + 后端 + 前端 + 扫码绑定流程)
---
### 3.2 SECURITY_ADMIN 角色
**PRD 要求 (§13.2):**
```
锁定策略、强制下线、审计检索;与 SYS_ADMIN 分离(职责分离)
权限矩阵: M11 中 F05F12、F21 及 M10 审计检索 RX
```
**当前缺口:**
- 角色代码未定义
- 路由/权限码未配置
- SECURITY_ADMIN 的专属 UI(会话管理页、锁定策略配置)
**预估工作量:** 小(1-2 天,角色定义 + 权限码 + 路由 + 会话管理页)
---
### 3.3 M11-F17 事业部数据范围 (Data Scope)
**PRD 要求:**
```
按事业部/区域/客户组限制列表可见行(与 M11-F18 二选一或组合)
```
**当前缺口:**
- 无数据范围模型(部门/区域/客户组表)
- 无 MyBatis-Plus 数据权限拦截器
- 前端无数据范围选择器
**实现思路:** MyBatis-Plus 的 `Interceptor``@SqlParser` 注解,在查询时自动追加数据范围条件。需要先定义组织/区域/客户组的基础数据模型。
**预估工作量:** 大(5-8 天,含数据模型 + 拦截器 + 配置 UI + 现有查询适配)
---
### 3.4 M10-F03 审计导出包
**PRD 要求:**
```
范围可选(项目/合同/时间窗),水印与权限
```
**当前缺口:**
- 后端 `GET /audit-events/export` 端点已存在(CSV 导出)
- 前端导出按钮未接入
- 无水印/权限控制
**预估工作量:** 小(0.5-1 天,前端按钮 + 参数传递)
---
### 3.5 M1-F09 CRM 主数据同步
**PRD 要求:**
```
以外部 ID 关联、增量同步状态展示
```
**当前缺口:**
- 完全未实现
- 无外部 ID 字段(已在 Customer entity 中有 `customerCode` 但非 CRM ID
- 无同步状态跟踪
**预估工作量:** 中(3-5 天,含外部 ID 模型 + 同步端点 + UI 状态展示)
---
### 3.6 细粒度互斥策略 (§13.5)
**PRD 要求:**
```
角色互斥规则(如 SYS_ADMIN 与业务高敏导出)
```
**当前缺口:**
- 完全未实现
- 当前仅简单串联角色权限
**预估工作量:** 中(2-3 天,互斥规则定义 + 后端校验 + 前端提示)
---
### 3.7 消息队列 (MQ) 架构
**架构要求:**
```
当前: Webhook → 直写 PostgreSQL → API 轮询
Full: Webhook → MQ → API 消费(削峰、DLQ、可观测)
```
**当前状态:**
- 无 MQ 基础设施(RabbitMQ/RocketMQ/Kafka
- Webhook 直写 inbox 表,API 轮询读取
- 已在 PRD 已知局限中标注(§16.6)
**预估工作量:** 大(5-10 天,含 MQ 选型 + 生产者/消费者 + 现有路径兼容 + DLQ)
---
### 3.8 读模型分离 (CQRS)
**架构要求:**
```
报表/查询从主库分离为独立读模型
```
**当前状态:**
- 全部查询直连主 PostgreSQL
- 无读模型/物化视图
**预估工作量:** 大(5-8 天,含读模型表 + 同步机制 + 现有查询迁移)
---
## 4. 总体评估
### 实现状态
| 分类 | 总项数 | ✅ 已完成 | ◐ 部分实现 | ○ 未开始 |
|------|--------|----------|-----------|---------|
| P2 功能点 | 13 | 0 | 3 (M5-F10, M9-F05, M9-F06) | 10 |
| Full 专项 | 8 | 0 | 0 | 8 |
| **合计** | **21** | **0 (0%)** | **3 (14%)** | **18 (86%)** |
### 工作量估算
| 项目 | 预估人天 | 依赖 |
|------|---------|------|
| M10-F03 审计导出按钮 | 0.5 | 无 |
| M5-F10 模拟投递 UI | 0.5 | 无 |
| SECURITY_ADMIN 角色 | 1 | 无 |
| M9-F05/F06 报表增强 | 1 | 无 |
| M2-F09 到期提醒 | 1 | M8 通知 |
| M6-F08/F09 版本/变更 | 2 | 无 |
| M11-F10 MFA | 4 | 无 |
| M1-F08 客户合并 | 3 | 无 |
| M1-F09 CRM 同步 | 4 | 无 |
| M11-F17 数据范围 | 6 | 组织模型 |
| MQ 消息队列 | 8 | 基础设施选型 |
| CQRS 读模型 | 7 | MQ 完成后 |
| **合计** | **~38 人天** | |
### 建议实施顺序
| 阶段 | 项目 | 人天 | 原因 |
|------|------|------|------|
| **Phase 1** | M10-F03 导出, M5-F10 模拟UI, SECURITY_ADMIN | 2 | 快速 wins,无依赖 |
| **Phase 2** | M9 报表推送, M6-F08/F09 版本矩阵 | 3 | 增强已有功能 |
| **Phase 3** | M11-F10 MFA, M11-F17 数据范围 | 10 | 安全核心能力 |
| **Phase 4** | M1-F08/F09 客户合并+CRM | 7 | 集成能力 |
| **Phase 5** | MQ + CQRS | 15 | 基础设施重构,依赖最重 |
@@ -0,0 +1,86 @@
# ONLYOFFICE 集成状态复盘
**日期:** 2026-05-27
**决策文档:** `docs/superpowers/specs/2026-05-25-onlyoffice-integration-decision.md`
---
## 1. 当前状态:规划阶段,零代码实现
| 维度 | 状态 |
|------|------|
| 设计决策 | ✅ 已完成(2026-05-25 |
| 后端实现 | ○ 未开始 |
| 前端实现 | ○ 未开始 |
| 文档代理层 | ○ 未开始 |
| ONLYOFFICE 服务部署 | 存在 `craftsupport.cn:8088`(已知可用) |
---
## 2. 决策回顾
| 决策项 | 结论 |
|--------|------|
| 是否集成 | ✅ 是,Mid 迭代完成后推进 |
| 集成范围 | **仅预览**,不做在线编辑 |
| 存储策略 | 附件本地存储,不接入 ONLYOFFICE 文档存储 |
| 优先级 | 非阻塞,排在 Mid (I10~I13) 之后 |
---
## 3. 实施前置条件检查
### 已完成(可直接利用)
| 条件 | 说明 |
|------|------|
| 合同附件上传 | **M2-F05 已实现** — 后端 `POST /contracts/{id}/attachments` + 前端附件列表 |
| 附件文件存储 | 文件存储在本地 `uploads/contracts/{id}/` |
| ONLYOFFICE 服务 | `craftsupport.cn:8088` 已知可用 |
### 未开始
| 组件 | 计划方案 | 预估 |
|------|---------|------|
| `DocumentPreviewController` | 平台新增代理端点:接收文件 ID → 返回 ONLYOFFICE 所需的配置 JSON + JWT | 1 天 |
| 前端预览弹窗 | 附件列表行操作加「预览」按钮,点击弹窗内嵌 ONLYOFFICE iframe | 0.5 天 |
| JWT 密钥配置 | `ONLYOFFICE_JWT_SECRET` 环境变量 | 0.1 天 |
| 文件流式输出 | ONLYOFFICE 通过平台 URL 获取文件内容 | 0.5 天 |
**合计预估:** 2 天
---
## 4. 阻碍因素
| 因素 | 说明 |
|------|------|
| **优先级排期** | 决策文档明确标注「Mid 迭代完成后推进」,当前 Tier 1+2 尚未全部完成 |
| **Mid 迭代未完成** | 当前工作仍在补齐 Tier 1 核心功能和 Tier 2 运营效率功能 |
| **ONLYOFFICE 服务可用性** | 需验证 `craftsupport.cn:8088` 当前是否可连接及 JWT 配置 |
---
## 5. 实施路线
```
Phase 1: 后端 DocumentPreviewController
GET /api/v1/preview/{attachmentId}
→ 返回 ONLYOFFICE 配置 JSON ({fileType, key, title, url, permissions: {download:false,edit:false}})
Phase 2: 前端预览弹窗
ContractDetailView.vue 附件列表 → 每行加「预览」按钮
→ 弹窗 iframe src = ONLYOFFICE 文档服务 URL + config
Phase 3: 文件流式服务
GET /api/v1/preview/{attachmentId}/file
→ 流式输出附件文件内容
```
---
## 6. 建议
1. **保持当前优先级** — Tier 1+2 业务功能完成后(预计 ~2 周)再启动 ONLYOFFICE
2. **验证 ONLYOFFICE 服务** — 启动前确认 `craftsupport.cn:8088` 可用并用 CORS 白名单允许平台域名
3. **最小实现** — 仅做 iframe 嵌入预览,不做编辑、不做保存回传,保持实现量最小
@@ -0,0 +1,211 @@
# PRD 实现进度复盘
**日期:** 2026-05-27
**PRD:** `docs/chuangfei-platform-product-modules.md`
---
## 1. 整体进度
| 状态 | 数量 | 占比 |
|------|------|------|
| ✅ 完全实现 | 24 | 32% |
| ◐ 部分实现 | 23 | 32% |
| ○ 未实现 | 26 | 36% |
| **合计** | **73** | **100%** |
```
M1 客户与项目中心 ██░░░░░░░░ 20% ✅ 2 ◐ 4 ○ 3
M2 合同与履约行 ████░░░░░░ 40% ✅ 4 ◐ 2 ○ 3
M3 交付管理 ██████░░░░ 60% ✅ 5 ◐ 0 ○ 3
M4 授权与许可运营 ██░░░░░░░░ 20% ✅ 3 ◐ 3 ○ 4
M5 Callback 运营 █████░░░░░ 50% ✅ 5 ◐ 2 ○ 2
M6 授权集成与配置 ████░░░░░░ 40% ✅ 4 ◐ 1 ○ 4
M7 设备与终端 █░░░░░░░░░ 10% ✅ 1 ◐ 3 ○ 2
M8 通知与待办 ░░░░░░░░░░ 0% ✅ 0 ◐ 3 ○ 2
M9 报表与对账 ░░░░░░░░░░ 0% ✅ 0 ◐ 5 ○ 1
M10 审计与合规 ██░░░░░░░░ 20% ✅ 1 ◐ 2 ○ 1
M11 身份/访问/平台 ███░░░░░░░ 30% ✅ 7 ◐ 3 ○11
```
---
## 2. 版本覆盖:MVP / Mid / Full
### MVP(I1~I9)— 标记为已完成 ✅
| 模块 | PRD 承诺 | 实际状态 |
|------|---------|---------|
| M1 客户项目 | P0 核心: 档案/列表/检索 | ✅ 完成, 缺详情聚合视图 |
| M2 合同 | 登记/状态机/行项 | ✅ 完成 |
| M3 交付 | 批次/清单/状态 | ✅ 完成 |
| M4 SN | 手工录入/绑定/状态 | ✅ 手工完成, 批量导入 UI 待补 |
| M5 Callback | 收件箱/详情/处置 | ✅ 完成 |
| M6 集成 | 产品线/环境只读 | ✅ 已超出(ID映射/JSON模板已实现) |
| M10 审计 | 关键字段变更日志 | ✅ 完成 |
| M11 身份 | JWT 登录/路由守卫/三角色/字典 | ◐ 路由级 RBAC ✅, 按钮级权限码未全覆盖 |
**MVP 已覆盖 P0 主链路:客户→项目→合同→交付→SN→Callback→审计**
### MidI10I13)— 进行中 🕐
| PRD 承诺 | 当前实现状态 |
|---------|-------------|
| M7 设备管理 | ◐ 设备登记/列表/详情已上线,换机审批/设备事件联动待补 |
| M8 通知待办 | ◐ 待办中心+通知配置已上线,实际发送逻辑未接入 |
| M9 报表对账 | ◐ 4 个报表页面已上线,导出按钮/推送逻辑待补 |
| 补齐 MVP 遗留 P0 | ◐ M1-F03 详情摘要后端已修复, M11-F03 空闲超时前端已实现 |
| M2/M4/M5/M6 P1 增强 | ○ 部分未开始 |
| M10-F02 审计检索 | ◐ AuditSearchView 已上线,筛选维度待确认 |
| M11 SSO/并发/强制下线 | ○ 未开始 |
| 角色模型对标产品定义集 | ◐ 4 角色已落地(ADMIN/SALES/DELIVERY/LICENSE_OPS), 仍有 6+ 角色未实现 |
### FullV2.0)— 规划中 📋
全部未开始:MFA、SECURITY_ADMIN、数据范围、审计导出包、CRM 同步。
---
## 3. 按模块的缺失功能点清单
### M1 客户与项目中心 — ✅2 ◐4 ○3
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F01 客户档案 | P0 | ◐ | 缺行业/地址/开票信息字段 |
| F03 详情聚合 | P0 | ◐ | 后端 `/summary` 已修复, 前端已展示 |
| F04 项目创建 | P0 | ◐ | 缺计划起止/项目经理字段 |
| F06 项目干系人 | P0 | ◐ | 后端 CRUD 就绪, 前端 UI 待补 |
| F07 冻结解冻 | P1 | ◐ | 后端就绪, 前端 UI 待补 |
| F08 客户合并 | P2 | ○ | 未开始 |
| F09 CRM 同步 | P2 | ○ | 未开始 |
### M2 合同与履约行 — ✅4 ◐2 ○3
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F05 合同附件 | P1 | ◐ | 后端上传就绪, 前端 UI 有待(已校验) |
| F07 合同变更 | P1 | ◐ | 后端 changes 就绪, 前端 UI 待补 |
| F08 SKU 映射 | P1 | ○ | 未实现 |
| F09 到期提醒 | P2 | ○ | 未实现 |
### M3 交付管理 — ✅5 ◐0 ○3
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F06 现场环境 | P1 | ○ | 未实现 |
| F07 SN 门禁 | P1 | ○ | `deliveryGateEnabled` 参数已定义但未执行 |
| F08 交付模板 | P2 | ○ | 未开始 |
### M4 授权与许可运营 — ✅3 ◐3 ○4
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F01 批量导入 | P0 | ◐ | 后端 `/batch-import` 就绪, 前端缺 UI |
| F05 原因码分类 | P0 | ◐ | 手工状态更新缺原因码 |
| F07 批量操作 | P1 | ◐ | 后端就绪, 前端缺批量 UI |
| F06 比特状态 | P1 | ○ | 依赖比特对接 |
| F08 授权需求单 | P1 | ○ | 未开始 |
| F09 试用/正式标签 | P1 | ○ | 未开始 |
| F11 策略视图 | P2 | ○ | 未开始 |
### M5 Callback 运营 — ✅5 ◐2 ○2
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F06 失败标注 | P1 | ○ | 未实现 |
| F07 批量重处理 | P1 | ◐ | 单条重放 ✅, 批量未做 |
| F08 死信监控 | P1 | ○ | 未实现 |
| F10 模拟投递 UI | P2 | ◐ | 后端 `/simulate` 就绪, 前端缺入口 |
### M6 授权集成与配置 — ✅4 ◐1 ○4
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F05 JSON 模板 | P1 | ◐ | CRUD ✅, Schema 校验未关联 UI |
| F06 发布记录 | P1 | ○ | 未实现 |
| F07 控制台链接 | P1 | ○ | 未实现 |
| F08 版本矩阵 | P2 | ○ | 未开始 |
| F09 变更分析 | P2 | ○ | 未开始 |
### M7 设备与终端 — ✅1 ◐3 ○2
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F01 设备登记 | P1 | ◐ | 登记/列表 ✅, 字段覆盖待确认 |
| F02 SN 绑定历史 | P1 | ◐ | 时间线已实现 |
| F03 换机审批 | P1 | ◐ | swap-request 端点就绪, 审批流未实现 |
| F05 Callback 联动 | P1 | ○ | 未实现 |
| F06 并发策略 | P2 | ○ | 未开始 |
### M8 通知与待办 — ✅0 ◐3 ○2
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F01 待办列表 | P1 | ◐ | 待办中心 ✅, 自动化待办生成未接入 |
| F02 认领完成 | P1 | ◐ | 状态流转 ✅, 备注功能待补 |
| F03 邮件/企微 | P1 | ◐ | 配置 UI ✅, 实际发送未接入 |
| F04 通知模板 | P2 | ○ | 未实现 |
| F05 静默规则 | P2 | ○ | 未开始 |
### M9 报表与对账 — ✅0 ◐5 ○1
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F01 合同 SN 视图 | P1 | ◐ | 已上线, 数据维度待确认 |
| F02 激活视图 | P1 | ○ | 未专门实现 |
| F03 Callback 统计 | P1 | ◐ | 已上线 |
| F04 导出按钮 | P1 | ◐ | 后端 `/export` 就绪, 前端缺按钮 |
| F05 项目健康度 | P2 | ◐ | 已上线, 规则可配置性待确认 |
| F06 订阅报表 | P2 | ◐ | 已上线, 推送逻辑待确认 |
### M10 审计与合规 — ✅1 ◐2 ○1
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F02 审计检索 | P1 | ◐ | AuditSearchView ✅, 端点有 500 错误需调试 |
| F03 审计导出 | P2 | ○ | 未实现 |
| F04 留存策略 | P2 | ◐ | AuditRetentionView ✅ |
### M11 身份/访问/平台 — ✅7 ◐3 ○11
| 功能点 | 优先级 | 当前状态 | 缺失内容 |
|--------|--------|---------|---------|
| F03 空闲超时 | P0 | ◐ | 前端 idleTimer 已实现, 后端会话管理未完成 |
| F05 失败锁定 | P0 | ✅ | 后端已有 5 次/15 分钟锁定 |
| F07 密码修改 | P0 | ✅ | Profile 页弹窗+后端端点 |
| F08 密码重置 | P1 | ✅ | 后端端点已实现(非空操作) |
| F09 SSO/OIDC | P1 | ○ | 未开始 |
| F11 并发会话 | P1 | ○ | 未开始(JWT 无状态) |
| F12 强制下线 | P1 | ◐ | 端点已实现, 需前端 UI |
| F14 用户管理 | P0 | ◐ | 表已创建, 管理页面未实现 |
| F15 角色定义 | P0 | ◐ | 4 角色已落地, 产品定义 10+ 需补齐 |
| F16 权限码 | P0 | ◐ | 路由级 ✅, 按钮级 `v-permission` 未全覆盖 |
| F17 数据范围 | P2 | ○ | 未开始 |
| F20 系统参数 | P1 | ◐ | SystemParamsView ✅, localStorage MVP |
| F21 敏感操作留痕 | P1 | ○ | 未开始 |
---
## 4. 已知 API 500 错误(需修复)
| 端点 | 影响模块 | 问题 |
|------|---------|------|
| `GET /api/v1/audit-events` | M10-F02 | 500 内部错误 |
| `GET /api/v1/integration/id-mappings` | M6-F03 | 500 内部错误 |
| `GET /api/v1/integration/feature-mappings` | M6-F04 | 500 内部错误 |
---
## 5. 总结
| 版本 | PRD 范围 | 实现状态 |
|------|---------|---------|
| **MVP** (I1~I9) | M1-M6 P0 + M10-F01 + M11 基础 | ✅ 主链路已完成 |
| **Mid** (I10~I13) | M7-M9 + P0补齐 + P1增强 | 🕐 M7/M8/M9 前端超前上线, 后端逻辑待补 |
| **Full** (V2.0) | 安全/合规/规模化 | 📋 全部未开始 |
**当前最优先缺口:**
1. 修复 3 个 500 错误 (audit-events, id-mappings, feature-mappings)
2. M11-F14 用户管理页面 (表已就绪)
3. M8-F03 通知发送实际对接
@@ -0,0 +1,176 @@
# UI 交互模式复盘 — 导航/弹框一致性 + 侧栏分组
**日期:** 2026-05-27
**范围:** 全部 38 个 Vue 视图
---
## 1. 当前 CRUD 模式盘点
| 页面 | Create | Edit | Detail | 评估 |
|------|--------|------|--------|------|
| **客户管理** | 弹框 `el-dialog` | 弹框 | 独立页面 | ✅ 一致 |
| **项目管理** | 弹框 `el-dialog` | 弹框 | 无独立详情(干系人管理在弹框) | ✅ 一致 |
| **合同管理** | 向导页 `/contracts/new` | 详情页内编辑 | 独立详情页 | ✅ 合理(多步) |
| **交付管理** | 向导页 `/deliveries/new` | 详情页内编辑 | 独立详情页 | ✅ 合理(多步) |
| **许可 SN** | 向导页 `/licenses/sn/new` | 详情页内编辑 | 独立详情页 | ✅ 合理 |
| **Callback** | 无(来源Webhook) | 详情页内操作 | 独立详情页 | ✅ |
| **设备管理** | 弹框 | 详情页内编辑 | 独立详情页 | ✅ 一致 |
| **用户管理** | 弹框 | 弹框 | 无独立详情 | ✅ 一致 |
### 结论:当前模式基本合理
| 场景 | 推荐模式 | 示例 |
|------|---------|------|
| **简单表单 (≤5 字段)** | **弹框** `el-dialog` | 客户、项目、设备、用户 |
| **复杂表单/向导 (≥2 步)** | **独立页面** | 合同向导、交付向导、SN 向导 |
| **详情展示** | **独立页面** | 合同详情、交付详情、SN 详情、设备详情 |
| **关联数据管理** | **弹框** | 项目干系人、SN 批量导入、合同明细行 |
**不建议强行统一为弹框** — 合同向导有 3 步(选择客户项目→填写信息→明细行),用弹框会撑爆视口。
---
## 2. 发现的不一致问题
| # | 问题 | 当前行为 | 建议 |
|---|------|---------|------|
| 1 | **合同列表行操作** | 「详情」跳转详情页,无直接「编辑」入口 | 详情页支持编辑即可(已有) |
| 2 | **交付列表行操作** | 同上 | 同上 |
| 3 | **SN 列表行操作** | 同上 | 同上 |
| 4 | **客户列表行操作** | 「详情」「编辑」「冻结」「删除」都在列表页 | ✅ 正确 |
| 5 | **设备列表无编辑** | 只有「详情」→详情页编辑 | 详情页已支持编辑,可接受 |
| 6 | **项目无独立详情页** | 编辑、干系人都在列表弹框 | 对于简单实体,弹框够用 ✅ |
---
## 3. 侧栏分组实现方案
### 当前结构(扁平)
```
📊 首页
👥 客户管理
📋 合同管理
📦 交付管理
🔑 许可 SN
🛡️ 许可证管理
📨 Callback 收件箱
🌐 集成环境
📱 产品线
🖥️ 设备管理
🔔 待办中心
📊 报表中心
📧 报表订阅
👤 用户管理
```
### 目标结构(三级分组)
```
📊 首页
──── 业务管理 ────
👥 客户管理
📋 合同管理
📦 交付管理
🔑 许可 SN
🛡️ 许可证管理
──── 运营管理 ────
📨 Callback 收件箱
🌐 集成环境
📱 产品线
🖥️ 设备管理
🔔 待办中心
──── 分析管理 ────
📊 报表中心
📧 报表订阅
──── 系统管理 ────
⚙️ 系统参数
👤 用户管理
🔐 审计日志
```
### 实现方式
**方案:** 修改 `MainLayout.vue` 中的 `menuItems` 数组为分组结构,模板中循环渲染组。
#### Step 1: 改造 menuItems 数据结构
```javascript
const menuGroups = [
{
label: '业务管理',
items: [
{ path: "/customers", icon: "👥", label: "客户管理", roles: ["SYS_ADMIN","SALES"] },
{ path: "/contracts", icon: "📋", label: "合同管理", roles: ["SYS_ADMIN","SALES"] },
{ path: "/deliveries", icon: "📦", label: "交付管理", roles: ["SYS_ADMIN","SALES","DELIVERY"] },
{ path: "/licenses/sn", icon: "🔑", label: "许可 SN", roles: ["SYS_ADMIN","SALES"] },
{ path: "/licenses", icon: "🛡️", label: "许可证管理", roles: ["SYS_ADMIN","SALES"] },
]
},
{
label: '运营管理',
items: [
{ path: "/callbacks", icon: "📨", label: "Callback 收件箱", roles: ["SYS_ADMIN","LICENSE_OPS"] },
{ path: "/integration/environments", icon: "🌐", label: "集成环境", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
{ path: "/integration/product-lines", icon: "📱", label: "产品线", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
{ path: "/devices", icon: "🖥️", label: "设备管理", roles: ["SYS_ADMIN","SALES","DELIVERY"] },
{ path: "/todos", icon: "🔔", label: "待办中心", roles: ["SYS_ADMIN","SALES","LICENSE_OPS"] },
]
},
{
label: '分析管理',
items: [
{ path: "/reports/contract-sn", icon: "📊", label: "报表中心", roles: ["SYS_ADMIN"] },
{ path: "/reports/subscriptions", icon: "📧", label: "报表订阅", roles: ["SYS_ADMIN"] },
]
},
{
label: '系统管理',
items: [
{ path: "/audit", icon: "🔐", label: "审计日志", roles: ["SYS_ADMIN"] },
{ path: "/admin/params", icon: "⚙️", label: "系统参数", roles: ["SYS_ADMIN"] },
{ path: "/admin/users", icon: "👤", label: "用户管理", roles: ["SYS_ADMIN"] },
]
},
];
```
#### Step 2: 改造模板渲染
```html
<aside class="app-sidebar">
<div class="sidebar-section-label">📊 首页</div>
<div class="sidebar-item" :class="{ active: isActive(homeItem) }" @click="$router.push(homeItem.path)">
<span class="sidebar-item-icon">{{ homeItem.icon }}</span>
<span class="sidebar-item-text">{{ homeItem.label }}</span>
</div>
<template v-for="group in visibleGroups" :key="group.label">
<div class="sidebar-group-label">{{ group.label }}</div>
<div
v-for="item in group.items"
:key="item.path"
:class="['sidebar-item', { active: isActive(item) }]"
@click="$router.push(item.path)"
>
<span class="sidebar-item-icon">{{ item.icon }}</span>
<span class="sidebar-item-text">{{ item.label }}</span>
</div>
</template>
<div class="sidebar-footer">CraftLabs Platform v0.1.0</div>
</aside>
```
#### Step 3: CSS 调整
```css
.sidebar-group-label {
font-size: 11px;
color: #999;
padding: 12px 16px 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
```
+19
View File
@@ -0,0 +1,19 @@
# 工程元数据(Engineering
本目录存放 **工作区边界说明****规划中的独立工程占位**,不引入额外构建步骤。
| 文件/路径 | 说明 |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `[workspace-manifest.json](workspace-manifest.json)` | 机器可读:当前组件路径、建议独立仓库列表 |
| `[planned/](planned/)` | 规划工程 README 占位(Webhook、平台 API、平台 UI |
| **系统架构全景(C4、时序、全量组件表、§9 安全架构)** | `[../docs/engineering/SYSTEM_ARCHITECTURE.md](../docs/engineering/SYSTEM_ARCHITECTURE.md)` |
| **OpenAPI 契约(SSOT** | `[../contracts/README.md](../contracts/README.md)` · `openapi/delivery-platform-api.json` |
| **平台部署 Runbook** | `[../services/RUNBOOK.md](../services/RUNBOOK.md)` |
| 详细架构走查 | `[../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md](../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)` |
| 三轨并行迭代(后端 / 前端 / SDK) | `[../docs/engineering/PARALLEL_ITERATION_INDEX.md](../docs/engineering/PARALLEL_ITERATION_INDEX.md)` · `[../docs/engineering/tracks/](../docs/engineering/tracks/)` |
**原则****授权 SDK**`java/``native/`)与 **商业交付平台**Spring + Vue)**分仓或分流水线**,避免服务端依赖 JNI/native 与客户端库混用。
**部署形态**:平台/Webhook 后端采用 **多模块开发 + 单 Fat JAR**(仅 bootstrap 模块 `repackage`);SDK 侧为 **多枚 library jar + native****不打** Spring Boot 可执行包 —— 详见 [WORKSPACE_ENGINEERING_LAYOUT.md §5](../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
+10
View File
@@ -0,0 +1,10 @@
# 规划中的工程(占位)
以下目录 **仅含 README**,用于对齐 [工作区工程划分文档](../../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)。
**实现时**:建议在独立 Git 仓库中创建项目,并将本仓库 **`schemas/`** 以 submodule、复制或 Maven/Gradle 资源依赖方式对齐。
| 子目录 | 规划职责 |
|--------|----------|
| [`license-webhook-ingress`](license-webhook-ingress) | 比特规则 Callback 接入 |
| [`delivery-platform-api`](delivery-platform-api) | 客户商务与交付管理平台后端 |
| [`delivery-platform-ui`](delivery-platform-ui) | 管理端前端 |
@@ -0,0 +1,35 @@
# delivery-platform-api(规划)
## 职责
实现 [客户商务与交付管理平台](../../../docs/chuangfei-platform-product-modules.md) 中 **M1M11** 领域 API(客户、项目、合同、交付、SN、Callback 处置、集成配置、设备、待办、报表、审计、身份与权限等)。
## 建议技术栈
- **Spring Boot 4.0.\***、Spring Security、**PostgreSQL 15**、**MyBatis-Plus**、Flyway/Liquibase。
- OpenAPIspringdoc);与 `license-webhook-ingress` 通过 **MQ/内部 API** 协作。
## 构建与部署:多模块开发 + 单 JAR 运维
**客户端 SDK** 严格区分(详见 [WORKSPACE_ENGINEERING_LAYOUT.md §5](../../../docs/engineering/WORKSPACE_ENGINEERING_LAYOUT.md)):
| 模式 | 做法 |
|------|------|
| **开发** | Maven **多模块**`domain``application-service``adapters-web``adapters-persistence`、**`bootstrap`(唯一 main** |
| **部署** | 仅 **`bootstrap`** 模块启用 `spring-boot-maven-plugin` **`repackage`**,产出 **一枚** 可执行 Fat JAR`java -jar platform-bootstrap.jar` |
| **构建命令示例** | `mvn -pl platform-bootstrap -am clean package` |
**禁止**:多个模块同时 `repackage`;在平台任意模块依赖 **`cn.craftlabs:craftlabs-auth-bitanswer`**(客户端 JNI SDK)。
## 与本工作区关系
- **可选**:将 [`schemas/craftlabs-auth-config.schema.json`](../../../schemas/craftlabs-auth-config.schema.json) 作为 **校验导出给现场的 JSON** 的资源(拷贝或构建时嵌入);**不**为引入 SDK 而把 `craftlabs-auth-parent` 作为父 POM。
- 若需共享纯配置 POJO:单独 **`craftlabs-auth-config-model`** 薄模块(无 native、无 `System.loadLibrary`),由 SDK 与平台 **分别****compileOnly/optional** 或复制方式对齐 —— 须经架构评审。
## 仓库建议名
`craftlabs-delivery-platform`
## Maven 坐标建议
- GroupId:如 `cn.craftlabs.platform`(与 `cn.craftlabs:craftlabs-auth-*` **区分**
@@ -0,0 +1,13 @@
# delivery-platform-ui(规划)
## 职责
客户商务与交付管理平台 **SPA**Vue 3 + Vite;对接 `delivery-platform-api` 的 OpenAPI;实现 [产品模块文档](../../../docs/chuangfei-platform-product-modules.md) 所列界面与权限(路由 meta + 后端 RBAC)。
## 与本工作区关系
- 无代码依赖;**文档与排期**见 [BPM 与 Roadmap](../../../docs/chuangfei-platform-bpm-and-roadmap.md)。
## 仓库建议名
`craftlabs-delivery-platform-ui`
@@ -0,0 +1,26 @@
# license-webhook-ingress(规划)
## 职责
- 接收比特授权云 **规则 Callback**HTTPS POST + JSON)。
- 校验 `x-bitanswer-token`(或约定头);**快速返回 2xx**;正文异步处理。
- 将事件写入 **消息队列****inbox 表**(与平台一致:**PostgreSQL 15** + **MyBatis-Plus**),由 `delivery-platform-api` 消费(或同仓 Worker 模块)。
## 建议技术栈
- **Java 21+**(与团队 JDK LTS 一致)、**Spring Boot 4.0.\***。
- 无 UI;可选 Spring Cloud Gateway 前置,或裸 Tomcat/Jetty + Actuator。
## 与本工作区关系
- **不依赖** `craftlabs-auth-bitanswer` native。
- 事件 Schema 对齐 [比特规则文档](../../../docs/bitanswer-licensing-design-and-rules.md) §5 与 [Callback 评估](../../../docs/bitanswer-callbacks-endpoints-assessment.md)。
## 构建与部署
- 与平台 API 相同策略:**Maven 多模块** + **唯一 `bootstrap` 模块** `spring-boot-maven-plugin` **repackage****单枚** `webhook-bootstrap.jar` 部署。
- **不依赖** `craftlabs-auth-bitanswer`
## 仓库建议名
`craftlabs-license-webhook`(可改名;与 `workspace-manifest.json``plannedRepositories` 保持一致即可)。
+72
View File
@@ -0,0 +1,72 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://craftlabs.cn/engineering/workspace-manifest.json",
"title": "craftlabs-authorization-sdk workspace manifest",
"workspace": "craftlabs-authorization-sdk",
"description": "客户端授权 SDK 与契约资产;商业交付平台与 Webhook 建议独立仓库,见 engineering/planned/",
"components": [
{
"id": "sdk-java",
"path": "java",
"type": "maven-multi-module",
"artifactId": "craftlabs-auth-parent",
"build": "mvn -f java/pom.xml verify"
},
{
"id": "sdk-native",
"path": "native",
"type": "cmake",
"primaryTarget": "craftlabs_auth_bitanswer"
},
{
"id": "auth-config-schema",
"path": "schemas",
"type": "json-schema",
"mainFile": "schemas/craftlabs-auth-config.schema.json"
},
{
"id": "examples",
"path": "examples",
"type": "samples"
},
{
"id": "documentation",
"path": "docs",
"type": "markdown"
},
{
"id": "engineering-meta",
"path": "engineering",
"type": "workspace-metadata"
}
],
"deploymentPrinciples": {
"clientSdk": "多枚 library JAR + native 动态库;不使用 spring-boot-maven-plugin repackage",
"platformBackend": "Maven 多模块开发;仅 bootstrap 模块 spring-boot-maven-plugin repackage 产出单枚可执行 Fat JARclasspath 禁止 craftlabs-auth-bitanswer"
},
"plannedRepositories": [
{
"id": "license-webhook-ingress",
"suggestedName": "craftlabs-license-webhook",
"stack": "Spring Boot 4.0.x",
"role": "BitAnswer rules Callback HTTPS ingress, token verify, MQ enqueue",
"stubPath": "engineering/planned/license-webhook-ingress",
"deployArtifact": "单 Fat JARwebhook-bootstrap 唯一 repackage"
},
{
"id": "delivery-platform-api",
"suggestedName": "craftlabs-delivery-platform",
"stack": "Spring Boot 4.0.x",
"role": "Customer, contract, delivery, SN, M5M11 domains",
"stubPath": "engineering/planned/delivery-platform-api",
"deployArtifact": "单 Fat JARplatform-bootstrap 唯一 repackage"
},
{
"id": "delivery-platform-ui",
"suggestedName": "craftlabs-delivery-platform-ui",
"stack": "Vue 3 + Vite",
"role": "Management console SPA",
"stubPath": "engineering/planned/delivery-platform-ui"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "floating",
"bitanswer": {
"url": "https://cloud.bitanswer.example/e3",
"loginMode": "REMOTE"
},
"features": {
"face": { "bitanswerFeatureId": 301 }
},
"floating": {
"projectId": "migrant-flow-prj-2026-001",
"projectName": "某市流动人口人像核验",
"contractRef": "PO-2026-8848"
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "school",
"bitanswer": {
"url": "https://cloud.bitanswer.example/e3",
"loginMode": "AUTO",
"sn": ""
},
"features": {
"face": { "bitanswerFeatureId": 201 },
"expression": { "bitanswerFeatureId": 202 }
},
"school": {
"edgeDeviceId": "classroom-gate-01",
"tenantId": "school-district-demo"
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"provider": "selfhosted",
"scenario": "school",
"selfhosted": {
"baseUrl": "https://license.internal.example/api/v1",
"tenantKey": "district-west",
"offlineGraceDays": 7,
"heartbeatIntervalHours": 24
},
"features": {
"face": { "selfhostedFeatureKey": "face" },
"expression": { "selfhostedFeatureKey": "expression" }
},
"school": {
"edgeDeviceId": "gate-02"
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "wharf",
"bitanswer": {
"url": "bit://license.example.com:8273",
"loginMode": "AUTO",
"rootPath": "/var/lib/craftlabs/bitanswer"
},
"features": {
"container_number_detect": { "bitanswerFeatureId": 101 },
"container_number_recognize": { "bitanswerFeatureId": 102 },
"iso_detect": { "bitanswerFeatureId": 103 },
"iso_recognize": { "bitanswerFeatureId": 104 }
},
"wharf": {
"topology": "group",
"groupServiceUrl": "bit://license.example.com:8273",
"notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
}
}
+29 -2
View File
@@ -1,9 +1,14 @@
import cn.craftlabs.auth.AuthProvider;
import cn.craftlabs.auth.AuthResult;
import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
import cn.craftlabs.auth.config.AuthConfig;
import cn.craftlabs.auth.config.AuthConfigException;
import cn.craftlabs.auth.config.AuthConfigs;
/**
* 演示:通过 {@link BitAnswerProvider} 完成初始化与许可校验。
* 演示:校验 {@code config_json} 后初始化并完成许可校验。
*
* <p>编译时需将 {@code craftlabs-auth-core} 与 Jackson 置于 classpath(与 Maven 模块依赖一致)。
*
* <p>版权所有 © 广州创飞人工智能技术有限公司
*
@@ -11,8 +16,30 @@ import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
*/
public class ExampleApp {
public static void main(String[] args) {
String json =
"""
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "school",
"bitanswer": { "url": "https://example.invalid/bitanswer", "loginMode": "AUTO" },
"features": {
"face": { "bitanswerFeatureId": 201 },
"expression": { "bitanswerFeatureId": 202 }
},
"school": { "edgeDeviceId": "demo-edge-01", "tenantId": "demo-tenant" }
}
""";
try {
AuthConfig cfg = AuthConfigs.parse(json);
System.out.println("scenario=" + cfg.scenario() + ", face id=" + cfg.bitanswerFeatureId("face"));
} catch (AuthConfigException e) {
System.err.println("config invalid: " + e.getMessage());
return;
}
try (AuthProvider p = new BitAnswerProvider()) {
AuthResult r = p.initialize("{}");
AuthResult r = p.initialize(json);
System.out.println("init: " + r.isSuccess() + " " + r.getMessage());
r = p.checkLicense();
System.out.println("check: " + r.isSuccess() + " " + r.getMessage());
+39
View File
@@ -0,0 +1,39 @@
# JAVA SDK
**Part of:** craftlabs-authorization-sdk
**Build:** Maven multi-module (JDK 17+)
## STRUCTURE
```
java/
├── craftlabs-auth-core/ # Core auth: config parsing, internal logic
├── craftlabs-auth-bitanswer/ # Bitanswer cloud licensing provider
├── craftlabs-auth-selfhosted/ # Self-hosted licensing provider
├── craftlabs-auth-tests/ # Integration tests
├── pom.xml # Parent aggregator POM
└── RELEASING.md # Release checklist & GPG signing guide
```
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
| Config model / schema | `craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/` | 9 files, config POJOs |
| Internal engine | `craftlabs-auth-core/src/main/java/cn/craftlabs/auth/internal/` | 3 files, core logic |
| Bitanswer provider | `craftlabs-auth-bitanswer/` | Single class, wraps bitanswer API |
| Selfhosted provider | `craftlabs-auth-selfhosted/` | Single class, local validation |
| Tests | `craftlabs-auth-tests/` | Config test cases |
| Release scripts | `scripts/sdk-release-checksums.sh` | SHA256 + GPG |
## CONVENTIONS
- **Package**: `cn.craftlabs.auth.*` (SDK) — distinct from `cn.craftlabs.platform.*` (backend)
- **No Spring Boot**: SDK modules are plain Java, no framework dependency
- **JNA bridge**: Java calls Rust native via JNA (not JNI)
- **Maven**: Parent POM at `java/pom.xml` aggregates sub-modules
## ANTI-PATTERNS
- **Do not** add Spring Boot / platform dependencies to SDK modules (keep plain Java)
- **Do not** put bitanswer/selfhosted specific logic into core module
+93
View File
@@ -0,0 +1,93 @@
# CraftLabs 授权 SDK — 发布与完整性
对齐架构文档 [SYSTEM_ARCHITECTURE §9.8](../docs/engineering/SYSTEM_ARCHITECTURE.md):官方渠道、**SHA-256 清单**、**GPG 签名(建议)**、`java``native` **同 Git tag**
## 1. 发布前检查
- [ ] `mvn -f java/pom.xml verify` 通过(JDK 17+)。
- [ ] `native` 已在各目标平台完成构建,且与本次 **同一 tag** 一并交付。
- [ ] `CHANGELOG`(或发布说明)写明 **比特 SDK / 运行时** 兼容版本。
## 2. 生成 SHA256SUMS(必做)
在仓库根目录执行(会先 `mvn -DskipTests package`):
```bash
chmod +x scripts/sdk-release-checksums.sh
./scripts/sdk-release-checksums.sh --output dist/sdk-release
```
已构建过时跳过 Maven
```bash
./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
```
**本机构建出的 Native** 一并写入清单(路径相对于仓库根,便于客户校验):
```bash
./scripts/sdk-release-checksums.sh --output dist/sdk-release --native-path "$(pwd)/native/build"
```
含完整测试的构建后再生成清单:
```bash
./scripts/sdk-release-checksums.sh --verify --output dist/sdk-release
```
输出:
- `dist/sdk-release/SHA256SUMS` — 每行:`哈希 相对路径`
- `dist/sdk-release/RELEASE-MANIFEST.txt` — 提交 SHA、UTC 时间
**客户校验**(在克隆/解压后的仓库根或同目录结构下):
```bash
sha256sum -c dist/sdk-release/SHA256SUMS
```
macOS
```bash
shasum -a 256 -c dist/sdk-release/SHA256SUMS
```
`SHA256SUMS` 本身做 **分离签名**(本机已配置 GPG):
```bash
SIGN=1 ./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
```
生成 `SHA256SUMS.asc`;客户使用公布的公钥:`gpg --verify SHA256SUMS.asc SHA256SUMS`
## 3. Maven JAR 的 GPG 签名(强烈建议)
父 POM 已配置 `maven-gpg-plugin`**默认跳过**`gpg.skip=true`),不影响日常 `verify`
发布前在已导入私钥的机器上:
```bash
gpg --version # 确认可用
mvn -f java/pom.xml -Prelease-sign verify
```
**可发布模块**`craftlabs-auth-core``craftlabs-auth-bitanswer``craftlabs-auth-selfhosted`)的 `target/*.jar` 旁会出现 **`.asc`**。`craftlabs-auth-tests` 模块固定 **不签名**
若无私钥或未就绪,可只发 **SHA256SUMS**;待密钥就绪后再打开 `-Prelease-sign`
### CI / 无人值守(可选)
在构建机配置 `MAVEN_GPG_PASSPHRASE` 等环境变量,或使用 `gpg-agent`;勿将私钥提交仓库。GitHub Actions 可用 `GPG_PRIVATE_KEY` secret + [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) 导入后再执行 `mvn -Prelease-sign verify`
## 4. GitHub Release 建议资产
每个 **tag** 上传:
1. 三个 **release JAR**(及对应 **.asc**,若已签名)
2. 各平台 **Native** 压缩包
3. **`SHA256SUMS`** 与 **`SHA256SUMS.asc`**
4. 固定页面公布 **GPG 公钥指纹** 与下载说明
---
版权所有 © 广州创飞人工智能技术有限公司(以项目实际声明为准)。
@@ -18,7 +18,7 @@ import cn.craftlabs.auth.internal.NativeBridge;
*/
public final class BitAnswerProvider implements AuthProvider {
static {
System.loadLibrary("craftlabs_auth_bitanswer");
System.loadLibrary("craftlabs_auth_core");
}
private long nativeHandle;
+21
View File
@@ -13,4 +13,25 @@
<artifactId>craftlabs-auth-core</artifactId>
<name>CraftLabs Auth — core API</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -3,6 +3,9 @@ package cn.craftlabs.auth;
/**
* 授权能力的统一契约:初始化、激活、校验许可、查询特性与释放等生命周期方法。
*
* <p>{@link #initialize(String)} 的 JSON 建议使用 {@link cn.craftlabs.auth.config.AuthConfigs#parse(String)}
* 先校验后再传入,格式见仓库 {@code schemas/craftlabs-auth-config.schema.json} 与 {@code examples/config/}。
*
* <p>实现类负责加载对应 native 或远端适配器;调用方应在不再使用时调用 {@link #close()} 释放底层资源。
*
* <p>版权所有 © 广州创飞人工智能技术有限公司
@@ -0,0 +1,47 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 所用 JSON 的配置模型,与 {@code
* schemas/craftlabs-auth-config.schema.json} 对齐。
*
* <p>版权所有 © 广州创飞人工智能技术有限公司
*
* @author huangping@craftlabs.cn
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record AuthConfig(
@JsonProperty("schemaVersion") int schemaVersion,
@JsonProperty("provider") String provider,
@JsonProperty("scenario") String scenario,
@JsonProperty("bitanswer") BitanswerConfigSection bitanswer,
@JsonProperty("selfhosted") SelfhostedConfigSection selfhosted,
@JsonProperty("features") Map<String, FeatureMapping> features,
@JsonProperty("wharf") WharfScenarioSection wharf,
@JsonProperty("school") SchoolScenarioSection school,
@JsonProperty("floating") FloatingScenarioSection floating) {
public AuthConfig {
features = features == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(features));
provider = Objects.requireNonNullElse(provider, "");
scenario = Objects.requireNonNullElse(scenario, "");
}
/** 逻辑特性键对应的比特特征 id;未配置或非数值映射时返回 {@code null}。 */
public Integer bitanswerFeatureId(String logicalFeatureKey) {
FeatureMapping m = features.get(logicalFeatureKey);
return m != null ? m.bitanswerFeatureId() : null;
}
/** 逻辑特性键对应的比特特征名称;未配置时返回 {@code null}。 */
public String bitanswerFeatureName(String logicalFeatureKey) {
FeatureMapping m = features.get(logicalFeatureKey);
return m != null ? m.bitanswerFeatureName() : null;
}
}
@@ -0,0 +1,19 @@
package cn.craftlabs.auth.config;
/**
* {@link AuthConfigs#parse(String)} 或校验失败时抛出。
*
* <p>版权所有 © 广州创飞人工智能技术有限公司
*
* @author huangping@craftlabs.cn
*/
public final class AuthConfigException extends Exception {
public AuthConfigException(String message) {
super(message);
}
public AuthConfigException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,111 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* 解析并校验 {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 的 JSON 配置。
*
* <p>版权所有 © 广州创飞人工智能技术有限公司
*
* @author huangping@craftlabs.cn
*/
public final class AuthConfigs {
/** 与 {@code schemas/craftlabs-auth-config.schema.json} 中 {@code schemaVersion} 一致。 */
public static final int SCHEMA_VERSION = 1;
private static final ObjectMapper MAPPER =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private AuthConfigs() {}
/** 解析 JSON 并执行 {@link #validate(AuthConfig)}。 */
public static AuthConfig parse(String json) throws AuthConfigException {
if (json == null || json.isBlank()) {
throw new AuthConfigException("config JSON is null or blank");
}
try {
AuthConfig cfg = MAPPER.readValue(json, AuthConfig.class);
validate(cfg);
return cfg;
} catch (JsonProcessingException e) {
String msg = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();
throw new AuthConfigException("Invalid config JSON: " + msg, e);
}
}
/** 将配置写回 JSON(便于日志脱敏后落盘或调试)。 */
public static String toJson(AuthConfig config) throws AuthConfigException {
try {
return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(config);
} catch (JsonProcessingException e) {
throw new AuthConfigException("Failed to serialize config", e);
}
}
/**
* 校验必填组合;通过后可安全交给 native / 供应商实现。
*
* @throws AuthConfigException 校验失败,消息为多条原因拼接
*/
public static void validate(AuthConfig c) throws AuthConfigException {
List<String> err = new ArrayList<>();
if (c.schemaVersion() != SCHEMA_VERSION) {
err.add("schemaVersion must be " + SCHEMA_VERSION);
}
String provider = norm(c.provider());
if (provider.isEmpty()) {
err.add("provider is required");
} else if (!provider.equals("bitanswer") && !provider.equals("selfhosted")) {
err.add("provider must be bitanswer or selfhosted");
}
String scenario = norm(c.scenario());
if (scenario.isEmpty()) {
err.add("scenario is required");
} else if (!scenario.equals("wharf") && !scenario.equals("school") && !scenario.equals("floating")) {
err.add("scenario must be wharf, school, or floating");
}
if (provider.equals("bitanswer")) {
if (c.bitanswer() == null) {
err.add("bitanswer section is required when provider=bitanswer");
} else if (isBlank(c.bitanswer().url())) {
err.add("bitanswer.url is required and must be non-blank");
}
}
if (provider.equals("selfhosted")) {
if (c.selfhosted() == null) {
err.add("selfhosted section is required when provider=selfhosted");
} else if (isBlank(c.selfhosted().baseUrl())) {
err.add("selfhosted.baseUrl is required and must be non-blank");
}
}
if (scenario.equals("floating")) {
if (c.floating() == null || isBlank(c.floating().projectId())) {
err.add("floating.projectId is required when scenario=floating");
}
}
if (!err.isEmpty()) {
throw new AuthConfigException(String.join("; ", err));
}
}
private static String norm(String s) {
return s == null ? "" : s.trim().toLowerCase(Locale.ROOT);
}
private static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
}
@@ -0,0 +1,17 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* {@code config_json} 中与比特安索客户端相关的字段子集。
*
* @author huangping@craftlabs.cn
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record BitanswerConfigSection(
@JsonProperty("url") String url,
@JsonProperty("loginMode") String loginMode,
@JsonProperty("rootPath") String rootPath,
@JsonProperty("sn") String sn,
@JsonProperty("applicationData") String applicationData) {}
@@ -0,0 +1,15 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 逻辑特性键(如 {@code face})到比特特征项的映射;至少填 id 或 name 之一即可在实现层选用。
*
* @author huangping@craftlabs.cn
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record FeatureMapping(
@JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
@JsonProperty("bitanswerFeatureName") String bitanswerFeatureName,
@JsonProperty("selfhostedFeatureKey") String selfhostedFeatureKey) {}
@@ -0,0 +1,11 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/** 流动人口按项目授权场景补充字段。 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public record FloatingScenarioSection(
@JsonProperty("projectId") String projectId,
@JsonProperty("projectName") String projectName,
@JsonProperty("contractRef") String contractRef) {}
@@ -0,0 +1,10 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/** 学校按边设备运营场景补充字段。 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SchoolScenarioSection(
@JsonProperty("edgeDeviceId") String edgeDeviceId,
@JsonProperty("tenantId") String tenantId) {}
@@ -0,0 +1,23 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* {@code provider=selfhosted} 时的 HTTP 后端参数。
*
* @author huangping@craftlabs.cn
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SelfhostedConfigSection(
@JsonProperty("baseUrl") String baseUrl,
@JsonProperty("tenantKey") String tenantKey,
@JsonProperty("offlineGraceDays") Integer offlineGraceDays,
@JsonProperty("heartbeatIntervalHours") Integer heartbeatIntervalHours,
@JsonProperty("publicKeyPem") String publicKeyPem) {
public SelfhostedConfigSection {
offlineGraceDays = offlineGraceDays != null ? offlineGraceDays : 7;
heartbeatIntervalHours = heartbeatIntervalHours != null ? heartbeatIntervalHours : 24;
}
}
@@ -0,0 +1,11 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/** 码头 / 集中授权场景补充字段。 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public record WharfScenarioSection(
@JsonProperty("topology") String topology,
@JsonProperty("groupServiceUrl") String groupServiceUrl,
@JsonProperty("notes") String notes) {}
@@ -0,0 +1,53 @@
package cn.craftlabs.auth.internal;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public interface CraftCoreLibrary extends Library {
CraftCoreLibrary INSTANCE = Native.load("craftlabs_auth_core", CraftCoreLibrary.class);
class CraftResult extends Structure {
public int success;
public String message;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("success", "message");
}
}
class LicenseInfoStruct extends Structure {
public int isLicensed;
public long expirationDate;
public Pointer featureNames;
public Pointer featureValues;
public int featureCount;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("isLicensed", "expirationDate", "featureNames", "featureValues", "featureCount");
}
}
Pointer craft_initialize(String configJson);
void craft_destroy(Pointer handle);
CraftResult craft_activate(Pointer handle, String licenseKey);
CraftResult craft_check_license(Pointer handle);
LicenseInfoStruct craft_get_license_info(Pointer handle);
void craft_free_license_info(LicenseInfoStruct info);
int craft_has_feature(Pointer handle, String featureName);
CraftResult craft_release(Pointer handle);
CraftResult craft_heartbeat(Pointer handle);
}
@@ -0,0 +1,89 @@
package cn.craftlabs.auth.internal;
import cn.craftlabs.auth.AuthProvider;
import cn.craftlabs.auth.AuthResult;
import cn.craftlabs.auth.LicenseInfo;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JnaAuthProvider implements AuthProvider {
private Pointer nativeHandle;
@Override
public AuthResult initialize(String configJson) {
if (nativeHandle != null) {
CraftCoreLibrary.INSTANCE.craft_destroy(nativeHandle);
}
nativeHandle = CraftCoreLibrary.INSTANCE.craft_initialize(
configJson != null ? configJson : "{}");
return new AuthResult(nativeHandle != null, "Initialized");
}
@Override
public AuthResult activate(String licenseKey) {
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_activate(
nativeHandle, licenseKey);
return new AuthResult(r.success != 0, r.message);
}
@Override
public AuthResult checkLicense() {
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_check_license(nativeHandle);
return new AuthResult(r.success != 0, r.message);
}
@Override
public LicenseInfo getLicenseInfo() {
CraftCoreLibrary.LicenseInfoStruct s = CraftCoreLibrary.INSTANCE.craft_get_license_info(nativeHandle);
if (s == null) {
return new LicenseInfo(false, null, null);
}
Map<String, Boolean> features = null;
if (s.featureCount > 0 && s.featureNames != null) {
features = new HashMap<>(s.featureCount);
for (int i = 0; i < s.featureCount; i++) {
Pointer namePtr = s.featureNames.getPointer((long) i * Native.POINTER_SIZE);
String name = namePtr != null ? namePtr.getString(0) : null;
if (name != null) {
int val = s.featureValues != null
? s.featureValues.getInt((long) i * Integer.BYTES)
: 0;
features.put(name, val != 0);
}
}
}
Date expirationDate = s.expirationDate > 0 ? new Date(s.expirationDate) : null;
CraftCoreLibrary.INSTANCE.craft_free_license_info(s);
return new LicenseInfo(s.isLicensed != 0, expirationDate, features);
}
@Override
public boolean hasFeature(String featureName) {
return CraftCoreLibrary.INSTANCE.craft_has_feature(nativeHandle, featureName) != 0;
}
@Override
public AuthResult release() {
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_release(nativeHandle);
return new AuthResult(r.success != 0, r.message);
}
@Override
public AuthResult heartbeat() {
CraftCoreLibrary.CraftResult r = CraftCoreLibrary.INSTANCE.craft_heartbeat(nativeHandle);
return new AuthResult(r.success != 0, r.message);
}
@Override
public void close() {
if (nativeHandle != null) {
CraftCoreLibrary.INSTANCE.craft_destroy(nativeHandle);
nativeHandle = null;
}
}
}
@@ -0,0 +1,78 @@
package cn.craftlabs.auth.config;
import static org.junit.jupiter.api.Assertions.*;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
class AuthConfigsTest {
@Test
void parseWharfExampleFromClasspath() throws Exception {
String json = readClasspath("examples/config/wharf.bitanswer.json");
AuthConfig c = AuthConfigs.parse(json);
assertEquals(1, c.schemaVersion());
assertEquals("bitanswer", c.provider());
assertEquals("wharf", c.scenario());
assertNotNull(c.bitanswer());
assertTrue(c.bitanswer().url().startsWith("bit://"));
assertEquals(101, c.bitanswerFeatureId("container_number_detect"));
assertEquals("group", c.wharf().topology());
}
@Test
void parseSchoolExample() throws Exception {
String json = readClasspath("examples/config/school.bitanswer.json");
AuthConfig c = AuthConfigs.parse(json);
assertEquals("school", c.scenario());
assertEquals(201, c.bitanswerFeatureId("face"));
assertEquals("classroom-gate-01", c.school().edgeDeviceId());
}
@Test
void parseFloatingExample_requiresProjectId() throws Exception {
String json = readClasspath("examples/config/floating.bitanswer.json");
AuthConfig c = AuthConfigs.parse(json);
assertEquals("floating", c.scenario());
assertEquals("migrant-flow-prj-2026-001", c.floating().projectId());
}
@Test
void validateFails_whenFloatingWithoutProject() {
String json =
"""
{"schemaVersion":1,"provider":"bitanswer","scenario":"floating",\
"bitanswer":{"url":"http://x"},"floating":{}}\
""";
AuthConfigException ex = assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
assertTrue(ex.getMessage().contains("projectId"));
}
@Test
void validateFails_whenBitanswerWithoutUrl() {
String json =
"""
{"schemaVersion":1,"provider":"bitanswer","scenario":"school",\
"bitanswer":{"url":""}}\
""";
assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
}
@Test
void roundTrip_toJson() throws Exception {
String json = readClasspath("examples/config/school.bitanswer.json");
AuthConfig c = AuthConfigs.parse(json);
String out = AuthConfigs.toJson(c);
AuthConfig again = AuthConfigs.parse(out);
assertEquals(c.scenario(), again.scenario());
assertEquals(c.bitanswerFeatureId("expression"), again.bitanswerFeatureId("expression"));
}
private static String readClasspath(String path) throws Exception {
try (InputStream in = AuthConfigsTest.class.getClassLoader().getResourceAsStream(path)) {
assertNotNull(in, "missing classpath resource: " + path);
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
@@ -0,0 +1,69 @@
package cn.craftlabs.auth.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* BP-10 / I5{@code examples/config/*.json} 与仓库根 {@code schemas/craftlabs-auth-config.schema.json} 一致。
*/
class ExamplesConfigSchemaValidationTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
void allExampleConfigsValidateAgainstSchema() throws Exception {
Path repoRoot = findRepoRoot();
Path schemaPath = repoRoot.resolve("schemas/craftlabs-auth-config.schema.json");
Path examplesDir = repoRoot.resolve("examples/config");
assertTrue(Files.isRegularFile(schemaPath), "schema missing: " + schemaPath);
assertTrue(Files.isDirectory(examplesDir), "examples dir missing: " + examplesDir);
JsonNode schemaNode = MAPPER.readTree(schemaPath.toFile());
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
JsonSchema schema = factory.getSchema(schemaNode);
try (Stream<Path> stream = Files.list(examplesDir)) {
stream.filter(p -> p.toString().endsWith(".json")).forEach(jsonFile -> {
try {
JsonNode instance = MAPPER.readTree(jsonFile.toFile());
Set<ValidationMessage> errors = schema.validate(instance);
assertTrue(
errors.isEmpty(),
() -> jsonFile.getFileName() + ": " + errors);
} catch (IOException e) {
throw new AssertionError(jsonFile.toString(), e);
}
});
}
}
/**
* 自 {@code user.dir} 向上查找含 {@code schemas/craftlabs-auth-config.schema.json} 的目录(兼容模块目录或仓库根执行)。
*/
static Path findRepoRoot() {
Path p = Path.of(System.getProperty("user.dir", ".")).toAbsolutePath().normalize();
for (int i = 0; i < 8 && p != null; i++) {
Path candidate = p.resolve("schemas/craftlabs-auth-config.schema.json");
if (Files.isRegularFile(candidate)) {
return p;
}
p = p.getParent();
}
throw new IllegalStateException(
"Could not find repo root (schemas/craftlabs-auth-config.schema.json) from user.dir="
+ System.getProperty("user.dir"));
}
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "floating",
"bitanswer": {
"url": "https://cloud.bitanswer.example/e3",
"loginMode": "REMOTE"
},
"features": {
"face": { "bitanswerFeatureId": 301 }
},
"floating": {
"projectId": "migrant-flow-prj-2026-001",
"projectName": "某市流动人口人像核验",
"contractRef": "PO-2026-8848"
}
}
@@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "school",
"bitanswer": {
"url": "https://cloud.bitanswer.example/e3",
"loginMode": "AUTO",
"sn": ""
},
"features": {
"face": { "bitanswerFeatureId": 201 },
"expression": { "bitanswerFeatureId": 202 }
},
"school": {
"edgeDeviceId": "classroom-gate-01",
"tenantId": "school-district-demo"
}
}
@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"provider": "bitanswer",
"scenario": "wharf",
"bitanswer": {
"url": "bit://license.example.com:8273",
"loginMode": "AUTO",
"rootPath": "/var/lib/craftlabs/bitanswer"
},
"features": {
"container_number_detect": { "bitanswerFeatureId": 101 },
"container_number_recognize": { "bitanswerFeatureId": 102 },
"iso_detect": { "bitanswerFeatureId": 103 },
"iso_recognize": { "bitanswerFeatureId": 104 }
},
"wharf": {
"topology": "group",
"groupServiceUrl": "bit://license.example.com:8273",
"notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
}
}
@@ -17,7 +17,7 @@ import cn.craftlabs.auth.internal.NativeBridge;
*/
public final class SelfHostedAuthProvider implements AuthProvider {
static {
System.loadLibrary("craftlabs_auth_bitanswer");
System.loadLibrary("craftlabs_auth_core");
}
private long nativeHandle;
+2
View File
@@ -28,6 +28,8 @@
</dependencies>
<properties>
<!-- 测试模块不发布;跳过 GPG 以免生成多余 .asc -->
<gpg.skip>true</gpg.skip>
<native.library.path>${project.basedir}/../../native/build</native.library.path>
</properties>
+52
View File
@@ -21,6 +21,10 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<junit.version>5.10.2</junit.version>
<jackson.version>2.17.2</jackson.version>
<json-schema-validator.version>1.5.7</json-schema-validator.version>
<!-- 默认跳过 GPG;发布签名:mvn -Prelease-sign verify(见 RELEASING.md -->
<gpg.skip>true</gpg.skip>
</properties>
<dependencyManagement>
@@ -36,6 +40,22 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${json-schema-validator.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -52,7 +72,39 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- 对发布 JAR 做 GPG 分离签名(.asc);显式 -Prelease-sign 时启用 -->
<profiles>
<profile>
<id>release-sign</id>
<properties>
<gpg.skip>false</gpg.skip>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-release-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

Some files were not shown because too many files have changed in this diff Show More