feat: 支持登录、个人信息存储
This commit is contained in:
496
ARCHITECTURE.md
Normal file
496
ARCHITECTURE.md
Normal file
@@ -0,0 +1,496 @@
|
||||
# Architecture Overview
|
||||
|
||||
## System Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ COCOS CREATOR GAME │
|
||||
│ WeChat Mini-Game Version 3.8.8 │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER (UI) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ PageLoading.ts ──────┐ │
|
||||
│ (Loading Screen) │ │
|
||||
│ │ │
|
||||
│ ┌──────────┐ │ ┌──────────┐ ┌──────────┐ │
|
||||
│ │PageHome │─────────┴──────│PageLevel │──────│PassModal │ │
|
||||
│ │(Menu) │ │ (Play) │ │(Victory) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ ▲ ▲ │ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────┬───────────┘ │ │
|
||||
│ │ (Back button) │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ Toast.ts ────────────────────────────────────────────────────────────│
|
||||
│ (Notifications) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CORE LOGIC LAYER │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ PageLevel.ts │ │ PassModal.ts │ │
|
||||
│ ├──────────────────────┤ ├──────────────────────┤ │
|
||||
│ │ • Answer validation │ │ • Next level │ │
|
||||
│ │ • Hint unlock (-life)│ │ • Share button │ │
|
||||
│ │ • Countdown (60s) │ │ • Callbacks │ │
|
||||
│ │ • Sound/Vibration │ │ │ │
|
||||
│ │ • Life display │ │ │ │
|
||||
│ └──────────────────────┘ └──────────────────────┘ │
|
||||
│ ▲ ▲ │
|
||||
│ │ │ │
|
||||
│ └────────────────┬───────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────┴──────────────────────────────────┐ │
|
||||
│ │ ViewManager.ts (View Navigation) │ │
|
||||
│ ├──────────────────────────────────────────────────────────────┤ │
|
||||
│ │ • Page registration & caching │ │
|
||||
│ │ • Page stack management (push/pop/replace) │ │
|
||||
│ │ • Lifecycle: onViewLoad → onViewShow → onViewHide → destroy │ │
|
||||
│ │ • Parameter passing between pages │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
│ BaseView.ts (Abstract base class for all pages) │
|
||||
│ • Lifecycle hooks │
|
||||
│ • Page state management │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ DATA & STATE MANAGEMENT LAYER │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ StorageManager.ts (Single Source of Truth) │ │
|
||||
│ ├──────────────────────────────────────────────────────────────┤ │
|
||||
│ │ Lives Management: │ │
|
||||
│ │ • getLives() / setLives() / consumeLife() / addLife() │ │
|
||||
│ │ │ │
|
||||
│ │ Progress Management: │ │
|
||||
│ │ • getCurrentLevelIndex() / getMaxUnlockedLevelIndex() │ │
|
||||
│ │ • onLevelCompleted(levelIndex) │ │
|
||||
│ │ • isLevelUnlocked(levelIndex) │ │
|
||||
│ │ │ │
|
||||
│ │ Storage Backend: sys.localStorage │ │
|
||||
│ │ └─ game_lives: string number (default: "10") │ │
|
||||
│ │ └─ game_progress: JSON with currentIndex & maxUnlockedIdx │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ LevelDataManager.ts (Level Data & Assets) │ │
|
||||
│ ├──────────────────────────────────────────────────────────────┤ │
|
||||
│ │ • initialize(onProgress) - Fetch API & load first level │ │
|
||||
│ │ • getLevelConfig(index) - Get level data + image │ │
|
||||
│ │ • ensureLevelReady(index) - On-demand level loading │ │
|
||||
│ │ • preloadNextLevel(currentIndex) - Async preload │ │
|
||||
│ │ │ │
|
||||
│ │ Memory Caches: │ │
|
||||
│ │ • _apiData: All levels from server │ │
|
||||
│ │ • _levelConfigs: Map of loaded level configs │ │
|
||||
│ │ • _imageCache: Map of loaded images (SpriteFrames) │ │
|
||||
│ │ • _loadingLevels: Set of levels being loaded │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ UTILITY & SDK LAYER │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────┐ │
|
||||
│ │ HttpUtil.ts │ │ WxSDK.ts │ │ToastManager.ts │ │
|
||||
│ ├────────────────────┤ ├────────────────────┤ ├──────────────────┤ │
|
||||
│ │ • get<T>() │ │ • isWechat() │ │ • init() │ │
|
||||
│ │ • post<T>() │ │ • initShare() │ │ • show() │ │
|
||||
│ │ │ │ • shareAppMessage()│ │ │ │
|
||||
│ │ Timeout: 10s │ │ • onShareAppMsg() │ │ Display duration │ │
|
||||
│ │ Error handling │ │ • vibrateShort() │ │ Fade out anim │ │
|
||||
│ │ │ │ • vibrateLong() │ │ │ │
|
||||
│ └────────────────────┘ └────────────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ EXTERNAL SYSTEMS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ WeChat Mini-Game SDK (wx global) │ │
|
||||
│ ├──────────────────────────────────────┤ │
|
||||
│ │ • wx.shareAppMessage() │ │
|
||||
│ │ • wx.onShareAppMessage() │ │
|
||||
│ │ • wx.showShareMenu() │ │
|
||||
│ │ • wx.vibrateShort() │ │
|
||||
│ │ • wx.vibrateLong() │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ Backend API Server │ │
|
||||
│ │ https://ilookai.cn │ │
|
||||
│ ├──────────────────────────────────────┤ │
|
||||
│ │ GET /api/v1/wechat-game/levels │ │
|
||||
│ │ • Returns: {success, data, message} │ │
|
||||
│ │ • Retry: 2x with 1s delay │ │
|
||||
│ │ • Timeout: 8s │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Diagram: Complete User Session
|
||||
|
||||
```
|
||||
START
|
||||
│
|
||||
├─ [main.ts] onLoad()
|
||||
│ └─ ViewManager.init(canvas)
|
||||
│ └─ ToastManager.init(toastPrefab)
|
||||
│ └─ Register PageHome & PageLevel
|
||||
│
|
||||
├─ [PageLoading.ts] start()
|
||||
│ ├─ LevelDataManager.initialize()
|
||||
│ │ ├─ HttpUtil.get() → API call
|
||||
│ │ │ └─ https://ilookai.cn/api/v1/wechat-game/levels
|
||||
│ │ ├─ Cache response → _apiData
|
||||
│ │ ├─ Load first level image
|
||||
│ │ │ └─ assetManager.loadRemote() → SpriteFrame
|
||||
│ │ │ └─ Cache in _imageCache
|
||||
│ │ └─ Return success
|
||||
│ │
|
||||
│ └─ ViewManager.preload('PageHome')
|
||||
│ └─ ViewManager.open('PageHome')
|
||||
│ └─ PageHome.onViewLoad()
|
||||
│ └─ WxSDK.initShare()
|
||||
│
|
||||
├─ [USER CLICKS "START GAME"]
|
||||
│
|
||||
├─ ViewManager.open('PageLevel', {params: {levelIndex: 0}})
|
||||
│ └─ PageLevel.onViewLoad()
|
||||
│ ├─ StorageManager.getCurrentLevelIndex()
|
||||
│ ├─ PageLevel.initLevel()
|
||||
│ │ ├─ LevelDataManager.ensureLevelReady(levelIndex)
|
||||
│ │ │ ├─ Check cache (_levelConfigs)
|
||||
│ │ │ ├─ If not cached: load image + create config
|
||||
│ │ │ └─ Store in cache
|
||||
│ │ │
|
||||
│ │ └─ PageLevel._applyLevelConfig()
|
||||
│ │ ├─ Display main image (sprite)
|
||||
│ │ ├─ Show Hint 1 (free)
|
||||
│ │ ├─ Hide Hints 2, 3 (show unlock buttons)
|
||||
│ │ ├─ Create input field based on answer.length
|
||||
│ │ ├─ Display lives: StorageManager.getLives()
|
||||
│ │ ├─ Start countdown (60s)
|
||||
│ │ └─ LevelDataManager.preloadNextLevel() [async]
|
||||
│
|
||||
├─ [USER INTERACTS]
|
||||
│
|
||||
├─ Option A: UNLOCK HINT
|
||||
│ ├─ PageLevel.onUnlockClue(2 or 3)
|
||||
│ ├─ Check: StorageManager.hasLives()?
|
||||
│ ├─ Yes → StorageManager.consumeLife() → display hint
|
||||
│ └─ No → Show "生命值不足" message
|
||||
│
|
||||
├─ Option B: SUBMIT ANSWER
|
||||
│ ├─ PageLevel.onSubmitAnswer()
|
||||
│ ├─ Validate: userAnswer === correctAnswer?
|
||||
│ │
|
||||
│ ├─ YES (Correct)
|
||||
│ │ ├─ PageLevel.showSuccess()
|
||||
│ │ ├─ Stop countdown
|
||||
│ │ ├─ Play successAudio
|
||||
│ │ ├─ StorageManager.addLife() [+1]
|
||||
│ │ ├─ PageLevel._showPassModal()
|
||||
│ │ │ ├─ Instantiate PassModal prefab
|
||||
│ │ │ ├─ PassModal.onViewLoad()
|
||||
│ │ │ ├─ PassModal.onViewShow()
|
||||
│ │ │ │ └─ Play success sound
|
||||
│ │ │ │ └─ Adjust widget to full screen
|
||||
│ │ │ │
|
||||
│ │ │ └─ User clicks button:
|
||||
│ │ │ ├─ "Next Level" → PageLevel.nextLevel()
|
||||
│ │ │ │ ├─ StorageManager.onLevelCompleted(currentIndex)
|
||||
│ │ │ │ │ ├─ currentLevelIndex++
|
||||
│ │ │ │ │ ├─ maxUnlockedLevelIndex = max(prev, current)
|
||||
│ │ │ │ │ └─ Save to localStorage
|
||||
│ │ │ │ ├─ Reload PageLevel with new index
|
||||
│ │ │ │ │ └─ Repeat from Level Setup
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └─ If last level:
|
||||
│ │ │ │ └─ ViewManager.back() → return to PageHome
|
||||
│ │ │ │
|
||||
│ │ │ └─ "Share" → WxSDK.shareAppMessage()
|
||||
│ │ │ └─ query: `level=${currentIndex+1}`
|
||||
│ │
|
||||
│ ├─ NO (Wrong)
|
||||
│ │ ├─ PageLevel.showError()
|
||||
│ │ ├─ Play failAudio
|
||||
│ │ ├─ WxSDK.vibrateLong() [device vibration]
|
||||
│ │ ├─ ToastManager.show("答案错误,再试试吧!")
|
||||
│ │ │ ├─ Create Toast node
|
||||
│ │ │ ├─ Show for 2000ms
|
||||
│ │ │ ├─ Fade out animation (300ms)
|
||||
│ │ │ └─ Destroy node
|
||||
│ │ │
|
||||
│ │ └─ Continue playing (allow retry)
|
||||
│
|
||||
├─ Option C: TIMEOUT
|
||||
│ └─ Countdown reaches 0
|
||||
│ ├─ PageLevel.onTimeUp()
|
||||
│ ├─ Play failAudio
|
||||
│ └─ [Incomplete: can still submit after timeout]
|
||||
│
|
||||
├─ [USER CLICKS BACK BUTTON]
|
||||
│ ├─ ViewManager.back()
|
||||
│ │ ├─ PageLevel._doHide()
|
||||
│ │ └─ PageHome._doShow()
|
||||
│ │
|
||||
│ └─ Return to home (progress saved in localStorage)
|
||||
│
|
||||
└─ END
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Flow
|
||||
|
||||
```
|
||||
USER PROGRESS STATE
|
||||
│
|
||||
├─ localStorage: game_progress
|
||||
│ └─ {
|
||||
│ "currentLevelIndex": 5,
|
||||
│ "maxUnlockedLevelIndex": 5
|
||||
│ }
|
||||
│
|
||||
├─ Memory Cache (LevelDataManager)
|
||||
│ ├─ _apiData: ApiLevelData[] (all levels)
|
||||
│ ├─ _levelConfigs: Map<levelIndex, RuntimeLevelConfig>
|
||||
│ ├─ _imageCache: Map<url, SpriteFrame>
|
||||
│ └─ _loadingLevels: Set<levelIndex> (currently loading)
|
||||
│
|
||||
└─ Session State (PageLevel component)
|
||||
├─ currentLevelIndex: number
|
||||
├─ _currentConfig: RuntimeLevelConfig
|
||||
├─ _countdown: number (60 → 0)
|
||||
├─ _isTimeUp: boolean
|
||||
├─ _isTransitioning: boolean
|
||||
└─ _passModalNode: Node | null
|
||||
|
||||
|
||||
LIVES STATE
|
||||
│
|
||||
├─ localStorage: game_lives
|
||||
│ └─ "10" (string representation of number)
|
||||
│
|
||||
├─ Cached value (StorageManager._progressCache)
|
||||
│ └─ Read on first access, cached for performance
|
||||
│
|
||||
└─ Operations (immediate storage update)
|
||||
├─ getLives() → read from storage
|
||||
├─ setLives(n) → validate & write to storage
|
||||
├─ consumeLife() → getLives() - 1, setLives()
|
||||
├─ addLife() → getLives() + 1, setLives()
|
||||
└─ hasLives() → getLives() > 0
|
||||
|
||||
|
||||
API CACHE
|
||||
│
|
||||
├─ First Load (Startup)
|
||||
│ ├─ HttpUtil.get(apiUrl, 8000ms timeout)
|
||||
│ ├─ Retry: 2 times (1s delay between)
|
||||
│ └─ Cache in LevelDataManager._apiData
|
||||
│
|
||||
└─ Per-Level Resources (On-Demand)
|
||||
├─ Check _levelConfigs cache
|
||||
├─ If missing: assetManager.loadRemote(imageUrl)
|
||||
├─ Create SpriteFrame
|
||||
└─ Cache in _imageCache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## View Stack & Navigation
|
||||
|
||||
```
|
||||
VIEW STACK (LIFO - Last In First Out)
|
||||
│
|
||||
├─ Level 0 (Bottom)
|
||||
│ └─ PageHome
|
||||
│
|
||||
├─ Level 1 (Middle)
|
||||
│ └─ PageLevel
|
||||
│
|
||||
├─ Level 2 (Top - Current)
|
||||
│ └─ PassModal (temporary, on PageLevel)
|
||||
│
|
||||
└─ Operations
|
||||
├─ open(viewId) → push to stack + show
|
||||
├─ back() → pop from stack + hide + show prev
|
||||
├─ replace(viewId) → pop + push new view
|
||||
└─ close() → pop + show previous
|
||||
|
||||
|
||||
CACHING BEHAVIOR
|
||||
│
|
||||
├─ PageHome
|
||||
│ ├─ cache: true
|
||||
│ └─ Cached after first open, reused on back
|
||||
│
|
||||
├─ PageLevel
|
||||
│ ├─ cache: true
|
||||
│ └─ Cached per instance (but reinitializes on each open)
|
||||
│
|
||||
└─ PassModal
|
||||
├─ Dynamically instantiated
|
||||
├─ Not cached
|
||||
└─ Destroyed after close
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
Component (Cocos)
|
||||
│
|
||||
├─ BaseView (Abstract base)
|
||||
│ ├─ PageHome
|
||||
│ ├─ PageLevel
|
||||
│ └─ PassModal
|
||||
│
|
||||
└─ Toast
|
||||
|
||||
|
||||
Managers (Singleton)
|
||||
│
|
||||
├─ ViewManager
|
||||
│ └─ Manages page lifecycle & navigation
|
||||
│
|
||||
├─ LevelDataManager
|
||||
│ └─ Manages API data & asset loading
|
||||
│
|
||||
├─ StorageManager
|
||||
│ └─ Manages user data persistence
|
||||
│
|
||||
├─ ToastManager
|
||||
│ └─ Manages toast notifications
|
||||
│
|
||||
└─ WxSDK
|
||||
└─ Manages WeChat integration
|
||||
|
||||
|
||||
Utilities
|
||||
│
|
||||
├─ HttpUtil
|
||||
│ └─ Static HTTP methods (GET/POST)
|
||||
│
|
||||
└─ LevelTypes
|
||||
└─ TypeScript interfaces for API data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
main.ts
|
||||
├─ ViewManager
|
||||
├─ ToastManager
|
||||
└─ [Page Prefabs]
|
||||
├─ PageHome
|
||||
│ └─ WxSDK
|
||||
│
|
||||
├─ PageLevel
|
||||
│ ├─ StorageManager
|
||||
│ ├─ WxSDK
|
||||
│ ├─ LevelDataManager
|
||||
│ ├─ ToastManager
|
||||
│ └─ PassModal
|
||||
│ ├─ WxSDK
|
||||
│ └─ BaseView
|
||||
│
|
||||
└─ PageLoading
|
||||
├─ ViewManager
|
||||
├─ LevelDataManager
|
||||
│ ├─ HttpUtil
|
||||
│ │ └─ XMLHttpRequest
|
||||
│ ├─ LevelTypes
|
||||
│ └─ Cocos assetManager
|
||||
└─ ToastManager
|
||||
|
||||
|
||||
EXTERNAL APIS
|
||||
│
|
||||
├─ https://ilookai.cn/api/v1/wechat-game/levels
|
||||
│ └─ Called by LevelDataManager._fetchApiData()
|
||||
│
|
||||
└─ WeChat SDK (wx global)
|
||||
├─ wx.shareAppMessage()
|
||||
├─ wx.onShareAppMessage()
|
||||
├─ wx.showShareMenu()
|
||||
├─ wx.vibrateShort()
|
||||
└─ wx.vibrateLong()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Memory Management
|
||||
- **Image Loading**: Remote images loaded on-demand with local caching
|
||||
- **Level Configs**: Loaded incrementally, current + next level only
|
||||
- **View Caching**: Pages cached after first load, reused on navigation back
|
||||
- **Message Queuing**: No event queue; direct method calls
|
||||
|
||||
### Network
|
||||
- **Single API Call**: Startup only (all levels fetched at once)
|
||||
- **Timeout**: 8 seconds for API requests
|
||||
- **Retry Logic**: 2 attempts with 1-second delay
|
||||
- **Cache Strategy**: In-memory cache, no expiration
|
||||
|
||||
### Storage
|
||||
- **localStorage**: 2 keys (lives + progress)
|
||||
- **No Batching**: Each update writes immediately
|
||||
- **Synchronous**: No async operations needed
|
||||
|
||||
### UI/Graphics
|
||||
- **Single Image**: Main puzzle image per level
|
||||
- **Dynamic Input**: Input field size adjusts based on answer length
|
||||
- **Audio**: One-shot playback, no loops
|
||||
- **Animations**: Toast fade-out only
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Current Implementation
|
||||
- ⚠️ All data stored locally (no encryption)
|
||||
- ⚠️ No user authentication
|
||||
- ⚠️ No server-side validation
|
||||
- ⚠️ Progress can be manually edited via localStorage
|
||||
|
||||
### Vulnerabilities
|
||||
1. localStorage can be inspected/modified via browser console
|
||||
2. No server-side checks on progress/lives
|
||||
3. Can modify localStorage to skip levels
|
||||
4. Can modify lives directly
|
||||
|
||||
### Improvements Needed
|
||||
- Server-side progress validation
|
||||
- User authentication
|
||||
- Encrypted storage
|
||||
- Server-side truth for lives/progress
|
||||
|
||||
Reference in New Issue
Block a user