# LinguaForge 開發指南 ## 快速開始 ### 前置需求 - Node.js 18+ LTS - PostgreSQL 14+ - Redis 7+ - React Native CLI - Xcode (iOS 開發) - Android Studio (Android 開發) ### 專案初始化步驟 #### 1. 克隆專案 ```bash git clone https://github.com/your-org/linguaforge.git cd linguaforge ``` #### 2. 後端設置 ```bash cd backend npm install cp .env.example .env # 編輯 .env 設定資料庫連線等 # 執行資料庫遷移 npm run migration:run # 啟動開發伺服器 npm run dev ``` #### 3. 前端設置 ```bash cd mobile npm install cd ios && pod install && cd .. # iOS npm run ios # Android npm run android ``` ## 開發流程 ### Git 分支策略 ``` main # 生產環境 ├── develop # 開發整合 ├── feature/card-generation # 功能開發 ├── feature/speech-assessment # 功能開發 └── hotfix/critical-bug # 緊急修復 ``` ### Commit 規範 ``` feat: 新增詞卡生成功能 fix: 修復複習排程計算錯誤 docs: 更新 API 文件 style: 調整程式碼格式 refactor: 重構認證模組 test: 新增單元測試 chore: 更新相依套件 ``` ## 核心功能實作指南 ### 1. SM-2 間隔重複演算法 ```typescript interface SM2Result { interval: number; repetition: number; easinessFactor: number; } function calculateSM2( quality: number, // 0-5 的評分 repetition: number, // 已複習次數 easinessFactor: number, // 難易度因子 interval: number // 當前間隔天數 ): SM2Result { // quality < 3 表示答錯,重置 if (quality < 3) { return { interval: 1, repetition: 0, easinessFactor }; } // 計算新的難易度因子 const newEF = Math.max(1.3, easinessFactor + 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02) ); // 計算新的間隔 let newInterval: number; if (repetition === 0) { newInterval = 1; } else if (repetition === 1) { newInterval = 6; } else { newInterval = Math.round(interval * newEF); } return { interval: newInterval, repetition: repetition + 1, easinessFactor: newEF }; } ``` ### 2. Gemini API 整合 ```typescript import { GoogleGenerativeAI } from '@google/generative-ai'; class CardGeneratorService { private genAI: GoogleGenerativeAI; constructor() { this.genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); } async generateCard(sentence: string, targetWord: string) { const model = this.genAI.getGenerativeModel({ model: "gemini-pro" }); const prompt = ` Given the sentence: "${sentence}" Target word: "${targetWord}" Generate a vocabulary card with: 1. Definition in Traditional Chinese 2. Part of speech 3. IPA pronunciation 4. 3 example sentences 5. Common collocations 6. Difficulty level (beginner/intermediate/advanced) Return as JSON format. `; const result = await model.generateContent(prompt); const response = await result.response; return JSON.parse(response.text()); } } ``` ### 3. Microsoft Speech Service 整合 ```typescript import * as sdk from 'microsoft-cognitiveservices-speech-sdk'; class PronunciationService { private speechConfig: sdk.SpeechConfig; constructor() { this.speechConfig = sdk.SpeechConfig.fromSubscription( process.env.SPEECH_KEY, process.env.SPEECH_REGION ); } async assessPronunciation( audioBuffer: Buffer, referenceText: string ): Promise { const audioConfig = sdk.AudioConfig.fromWavFileInput(audioBuffer); const pronunciationConfig = new sdk.PronunciationAssessmentConfig( referenceText, sdk.PronunciationAssessmentGradingSystem.HundredMark, sdk.PronunciationAssessmentGranularity.Phoneme, true ); const recognizer = new sdk.SpeechRecognizer( this.speechConfig, audioConfig ); pronunciationConfig.applyTo(recognizer); return new Promise((resolve, reject) => { recognizer.recognizeOnceAsync( result => { const pronunciationResult = sdk.PronunciationAssessmentResult .fromResult(result); resolve({ accuracyScore: pronunciationResult.accuracyScore, fluencyScore: pronunciationResult.fluencyScore, completenessScore: pronunciationResult.completenessScore, pronunciationScore: pronunciationResult.pronunciationScore }); }, error => reject(error) ); }); } } ``` ### 4. 離線資料同步 ```typescript class OfflineSyncService { async syncData() { // 1. 檢查網路連線 const isOnline = await NetInfo.fetch(); if (!isOnline.isConnected) return; // 2. 取得本地待同步資料 const pendingChanges = await localDB.getPendingChanges(); // 3. 批量上傳變更 const syncPromises = pendingChanges.map(change => { switch (change.type) { case 'CREATE': return api.createCard(change.data); case 'UPDATE': return api.updateCard(change.id, change.data); case 'DELETE': return api.deleteCard(change.id); } }); // 4. 處理同步結果 const results = await Promise.allSettled(syncPromises); // 5. 標記成功同步的項目 results.forEach((result, index) => { if (result.status === 'fulfilled') { localDB.markAsSynced(pendingChanges[index].id); } }); // 6. 下載伺服器端更新 const serverUpdates = await api.getUpdates(lastSyncTime); await localDB.applyServerUpdates(serverUpdates); } } ``` ## 測試策略 ### 單元測試範例 ```typescript describe('SM2 Algorithm', () => { it('should reset interval when quality < 3', () => { const result = calculateSM2(2, 5, 2.5, 10); expect(result.interval).toBe(1); expect(result.repetition).toBe(0); }); it('should increase interval for good performance', () => { const result = calculateSM2(4, 2, 2.5, 6); expect(result.interval).toBeGreaterThan(6); }); }); ``` ### E2E 測試範例 ```typescript describe('Card Generation Flow', () => { it('should generate card from sentence', async () => { await element(by.id('new-card-button')).tap(); await element(by.id('sentence-input')).typeText( 'I need to abandon this habit' ); await element(by.text('abandon')).tap(); await element(by.id('generate-button')).tap(); await waitFor(element(by.id('card-preview'))) .toBeVisible() .withTimeout(5000); await expect(element(by.text('放棄'))).toBeVisible(); }); }); ``` ## 效能優化建議 ### 1. React Native 優化 ```javascript // 使用 memo 優化重渲染 const CardItem = React.memo(({ card, onPress }) => { return ( onPress(card.id)}> {card.word} ); }, (prevProps, nextProps) => { return prevProps.card.id === nextProps.card.id; }); // 使用 FlatList 優化長列表 item.id} renderItem={({ item }) => } windowSize={10} initialNumToRender={10} maxToRenderPerBatch={10} removeClippedSubviews={true} /> ``` ### 2. API 優化 ```typescript // 批量請求 router.post('/cards/batch', async (req, res) => { const operations = req.body.operations; const results = await Promise.all( operations.map(op => processOperation(op)) ); res.json({ results }); }); // 資料快取 @Injectable() export class CardService { constructor( @InjectRedis() private redis: Redis, @InjectRepository(Card) private cardRepo: Repository ) {} async getCard(id: string) { // 檢查快取 const cached = await this.redis.get(`card:${id}`); if (cached) return JSON.parse(cached); // 從資料庫取得 const card = await this.cardRepo.findOne(id); // 寫入快取 await this.redis.set( `card:${id}`, JSON.stringify(card), 'EX', 3600 ); return card; } } ``` ## 監控與除錯 ### 日誌配置 ```typescript import winston from 'winston'; const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }); if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); } ``` ### Sentry 整合 ```typescript import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0 }); // 錯誤捕獲中間件 app.use((err, req, res, next) => { Sentry.captureException(err); res.status(500).json({ error: 'Internal server error' }); }); ``` ## 部署檢查清單 ### 部署前檢查 - [ ] 所有測試通過 - [ ] 程式碼審查完成 - [ ] 更新版本號 - [ ] 更新 CHANGELOG - [ ] 環境變數配置正確 - [ ] 資料庫遷移準備就緒 - [ ] API 文件更新 - [ ] 監控告警設置 ### 部署步驟 ```bash # 1. 建立 Docker 映像 docker build -t linguaforge-api:v1.0.0 . # 2. 推送至 registry docker push registry.example.com/linguaforge-api:v1.0.0 # 3. 更新 Kubernetes 部署 kubectl set image deployment/api api=registry.example.com/linguaforge-api:v1.0.0 # 4. 監控部署狀態 kubectl rollout status deployment/api # 5. 執行煙霧測試 npm run test:smoke ``` ## 常見問題排查 ### 問題: iOS 建置失敗 ```bash # 清理快取 cd ios rm -rf Pods Podfile.lock pod install --repo-update cd .. npm run ios -- --reset-cache ``` ### 問題: Android 建置失敗 ```bash # 清理專案 cd android ./gradlew clean cd .. npm run android -- --reset-cache ``` ### 問題: 資料庫連線失敗 ```typescript // 檢查連線池配置 { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, synchronize: false, logging: true, entities: ['dist/**/*.entity.js'], migrations: ['dist/migrations/*.js'], extra: { max: 20, // 連線池大小 idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, } } ```