技术架构
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 安全
- JWT 认证: 所有 API 请求必须携带有效 Token
- CORS 控制: 限制允许的来源域名
- 速率限制: 防止 API 滥用
- SQL 注入防护: Drupal Entity API 自动参数化查询
数据安全
- 租户隔离: 数据表级别的物理隔离
- 权限控制: 基于角色的访问控制 (RBAC)
- 文件访问控制: 文件下载需要权限验证
- 加密传输: 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