Skip to Content
🚀 Drubase One v1.1 - 基于 Drupal 11 的多租户后端即服务平台

技术架构

Groups 应用基于现代化的全栈架构,充分利用 Drubase One 的 BaaS 能力。

🏗️ 整体架构

┌─────────────────────────────────────────────────┐ │ 前端层 (Frontend) │ │ React Native Web + Expo (iOS/Android/Web) │ │ • Context API 状态管理 │ │ • Service Layer 封装 │ │ • WebSocket 客户端 │ └─────────────────┬───────────────────────────────┘ │ HTTPS + WebSocket ┌─────────────────▼───────────────────────────────┐ │ BaaS 后端 (Drubase One) │ │ ┌──────────────────────────────────────────┐ │ │ │ Nginx (反向代理 + SSL) │ │ │ └──────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────▼───────────────────────────┐ │ │ │ Drupal 11 (PHP 8.4) │ │ │ │ • baas_tenant - 租户管理 │ │ │ │ • baas_project - 项目管理 │ │ │ │ • baas_entity - 动态实体 │ │ │ │ • baas_api - RESTful API │ │ │ │ • baas_auth - JWT 认证 │ │ │ │ • baas_file - 文件管理 │ │ │ └──────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────▼───────────────────────────┐ │ │ │ Node.js 服务层 (TypeScript) │ │ │ │ • AlgorithmService - 智能分组算法 │ │ │ │ • FileService - 文件上传处理 │ │ │ │ • RealtimeService - WebSocket 实时推送 │ │ │ │ • PosterService - 海报生成 │ │ │ └──────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────▼───────────────────────────┐ │ │ │ PostgreSQL 17 + Redis 7 │ │ │ │ • 多租户数据隔离 │ │ │ │ • LISTEN/NOTIFY 实时通知 │ │ │ │ • 缓存和会话存储 │ │ │ └──────────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘

🔐 认证流程

JWT 认证机制

// 1. 用户登录 POST /api/user/login { "username": "user@example.com", "password": "password123" } // 2. 返回 JWT Token { "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "uid": 123, "name": "John Doe", "email": "user@example.com" } } } // 3. 后续请求携带 Token GET /api/baas/activities Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Token 刷新策略

  • Token 有效期: 3600 秒 (1 小时)
  • 自动刷新: 前端在 Token 过期前 5 分钟自动刷新
  • 刷新端点: POST /api/user/token/refresh

📦 数据层设计

多租户数据隔离

Groups 使用 Drubase One 的多租户架构:

tenant_7375b0cd # 租户 ID └── tenant_7375b0cd_project_6888d012be80c # 项目 ID ├── baas_856064_users # 用户实体表 ├── baas_856064_activities # 活动实体表 ├── baas_856064_teams # 队伍实体表 ├── baas_856064_positions # 位置实体表 └── baas_856064_user_activities # 用户活动关系表

表名规则: baas_{6hash}_{entity_name}

  • 6hash: tenant_id + project_id 的 MD5 前 6 位
  • entity_name: 实体名称

核心实体关系

┌─────────────┐ │ Users │ │ (用户) │ └──────┬──────┘ │ 1 │ N ┌──────▼──────────┐ │ UserActivities │ │ (用户活动关系) │ └──────┬──────────┘ │ N │ 1 ┌──────▼──────┐ ┌──────────┐ │ Activities │───────│ Teams │ │ (活动) │ N:N │ (队伍) │ └─────────────┘ └────┬─────┘ │ 1 │ N ┌────▼─────┐ │ Positions│ │ (位置) │ └──────────┘

⚙️ 服务层架构

Node.js 服务模块

// ServiceFactory 模式 class ServiceFactory { static algorithmService: AlgorithmService; static fileService: FileService; static realtimeService: RealtimeService; static posterService: PosterService; static getAlgorithmService(): AlgorithmService { if (!this.algorithmService) { this.algorithmService = new AlgorithmService(); } return this.algorithmService; } // ...其他服务 }

智能分组算法

interface TeamAllocation { teamId: string; userId: string; probability: number; } class AlgorithmService { /** * 计算队伍分配概率 * 概率 = (1 - 占用率^factor) × (可用座位/总座位)^(1/factor) */ calculateProbability( team: Team, occupancyFactor: number = 2.0 ): number { const occupancyRate = team.currentMembers / team.maxMembers; const availableSeats = team.maxMembers - team.currentMembers; const totalAvailable = this.getTotalAvailableSeats(); return ( Math.pow(1 - occupancyRate, occupancyFactor) * Math.pow(availableSeats / totalAvailable, 1 / occupancyFactor) ); } /** * 分配用户到队伍 */ allocateUserToTeam(userId: string, teams: Team[]): TeamAllocation { const probabilities = teams.map(team => ({ teamId: team.id, userId, probability: this.calculateProbability(team) })); // 归一化概率 const totalProb = probabilities.reduce((sum, p) => sum + p.probability, 0); const normalized = probabilities.map(p => ({ ...p, probability: p.probability / totalProb })); // 轮盘赌选择 return this.rouletteWheelSelection(normalized); } }

🔄 实时同步机制

PostgreSQL LISTEN/NOTIFY

-- 创建通知函数 CREATE OR REPLACE FUNCTION notify_entity_change() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify( 'entity_changes', json_build_object( 'table', TG_TABLE_NAME, 'action', TG_OP, 'data', row_to_json(NEW) )::text ); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 创建触发器 CREATE TRIGGER activities_notify AFTER INSERT OR UPDATE OR DELETE ON baas_856064_activities FOR EACH ROW EXECUTE FUNCTION notify_entity_change();

Node WebSocket 服务

class RealtimeService { private wss: WebSocketServer; private pgClient: Client; constructor() { this.setupPostgreSQLListener(); this.setupWebSocketServer(); } private setupPostgreSQLListener() { this.pgClient.on('notification', (msg) => { const payload = JSON.parse(msg.payload); this.broadcastToClients(payload); }); this.pgClient.query('LISTEN entity_changes'); } private broadcastToClients(payload: any) { this.wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'entity_change', data: payload })); } }); } }

📱 前端架构

Context + Service Layer

// Context API 状态管理 interface AppContextType { user: User | null; activities: Activity[]; teams: Team[]; } export const AppContext = createContext<AppContextType>(null); // Service Layer 封装 class ActivityService { private baseUrl = 'http://YOUR_HOST/api'; async getActivities(): Promise<Activity[]> { const response = await fetch(\`\${this.baseUrl}/baas/activities\`, { headers: { 'Authorization': \`Bearer \${getToken()}\` } }); return response.json(); } async createActivity(data: ActivityInput): Promise<Activity> { const response = await fetch(\`\${this.baseUrl}/baas/activities\`, { method: 'POST', headers: { 'Authorization': \`Bearer \${getToken()}\`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } }

WebSocket 客户端

class RealtimeClient { private ws: WebSocket; connect() { this.ws = new WebSocket('ws://YOUR_HOST:3001'); this.ws.onmessage = (event) => { const message = JSON.parse(event.data); this.handleMessage(message); }; } subscribe(channel: string) { this.ws.send(JSON.stringify({ action: 'subscribe', channel })); } private handleMessage(message: any) { if (message.type === 'entity_change') { // 更新本地状态 this.updateLocalState(message.data); } } }

🔒 安全架构

API 安全

  1. JWT 认证: 所有 API 请求必须携带有效 Token
  2. CORS 控制: 限制允许的来源域名
  3. 速率限制: 防止 API 滥用
  4. SQL 注入防护: Drupal Entity API 自动参数化查询

数据安全

  1. 租户隔离: 数据表级别的物理隔离
  2. 权限控制: 基于角色的访问控制 (RBAC)
  3. 文件访问控制: 文件下载需要权限验证
  4. 加密传输: HTTPS + WSS

🚀 性能优化

缓存策略

# Redis 缓存层次 L1: Application Cache (Drupal Cache API) - 实体查询结果 (5分钟) - 配置数据 (1小时) L2: Redis Cache - 用户会话 (1小时) - API 响应 (可配置) - 实时数据快照 (30秒)

数据库优化

-- 索引优化 CREATE INDEX idx_activities_created ON baas_856064_activities(created); CREATE INDEX idx_teams_activity ON baas_856064_teams(activity_id); CREATE INDEX idx_positions_team ON baas_856064_positions(team_id); -- 查询优化 -- 使用 JOIN 替代多次查询 SELECT a.*, t.*, p.* FROM baas_856064_activities a LEFT JOIN baas_856064_teams t ON t.activity_id = a.id LEFT JOIN baas_856064_positions p ON p.team_id = t.id WHERE a.id = $1;

📊 监控和日志

应用监控

  • 性能监控: API 响应时间、数据库查询时间
  • 错误追踪: 异常日志收集和分析
  • 用户行为: 关键操作的使用统计

日志系统

logs/ ├── drupal/ │ ├── error.log # PHP 错误日志 │ └── watchdog.log # Drupal 系统日志 ├── nodejs/ │ ├── app.log # 应用日志 │ ├── algorithm.log # 算法执行日志 │ └── realtime.log # 实时通信日志 └── nginx/ ├── access.log # 访问日志 └── error.log # Nginx 错误日志

📖 相关文档

Last updated on