呆鸟修仙 好友系统


# 呆鸟修仙 好友系统

2.png

本文档详细说明好友系统的功能、数据逻辑和关键代码。

## 功能概述

好友系统支持用户通过邀请码注册自动建立好友关系,实现社交功能。关系为双向,支持邀请计数和奖励机制。

## 系统架构

### 数据模型

```
users (用户表)
    ├── inviter_id (邀请人ID)
    └── invite_count (累计邀请人数)

friendships (好友关系表)
    ├── user_id (用户ID)
    ├── friend_id (好友ID)
    └── status (状态)
```

### 关系类型

1. **自动好友**:通过邀请码注册自动建立
2. **双向关系**:A和B是好友,则存在两条记录:
   - A → B
   - B → A

## 数据库表结构

### friendships 表

**字段说明**:

| 字段名 | 类型 | 约束 | 说明 |
|--------|------|------|------|
| id | varchar(36) | PRIMARY KEY | 关系UUID |
| user_id | varchar(36) | NOT NULL, FK | 用户ID |
| friend_id | varchar(36) | NOT NULL, FK | 好友ID |
| status | varchar(20) | DEFAULT 'accepted' | 状态:pending/accepted |
| created_at | timestamp | DEFAULT NOW() | 创建时间 |

**唯一约束**:
- `(user_id, friend_id)` - 复合唯一约束,防止重复关系

**索引**:
- `friendships_user_friend_idx`: (user_id, friend_id)
- `friendships_friend_user_idx`: (friend_id, user_id)

## 业务流程

### 1. 注册时自动建立好友关系

**流程**:
```
新用户通过邀请码注册
    ↓
验证邀请码有效性
    ↓
【事务开始】
    ├─ 创建用户记录 (users.inviter_id = 邀请人ID)
    ├─ 建立好友关系: 用户 → 邀请人
    └─ 建立好友关系: 邀请人 → 用户
【事务提交】
```

**代码位置**: `src/app/api/auth/register/route.ts:65-71`

```typescript
// 建立双向好友关系
await tx.insert(friendships).values({
  userId: user.id,
  friendId: inviter.id,
  status: "accepted",
});
await tx.insert(friendships).values({
  userId: inviter.id,
  friendId: user.id,
  status: "accepted",
});
```

### 2. 查询好友列表

**查询逻辑**:
```sql
SELECT u.*
FROM users u
JOIN friendships f ON u.id = f.friend_id
WHERE f.user_id = ${userId}
  AND f.status = 'accepted'
ORDER BY f.created_at DESC
```

### 3. 统计好友数量

```sql
SELECT COUNT(*)
FROM friendships
WHERE user_id = ${userId}
  AND status = 'accepted'
```

### 4. 邀请计数

**更新邀请人计数**(注册时):
```typescript
await db.execute(sql`
  UPDATE users
  SET invite_count = invite_count + 1
  WHERE id = ${inviterId}
`);
```

**每日邀请限制**:
- `daily_invite_limit`: 每日最大邀请数(默认2)
- `daily_invite_count`: 今日已邀请数
- `last_invite_date`: 最后邀请日期(YYYY-MM-DD)

## API接口

### 1. 获取好友列表

**接口**: `GET /api/users/:id/friends`

**响应示例**:
```json
{
  "success": true,
  "friends": [
    {
      "id": "uuid",
      "username": "好友用户名",
      "inviteCode": "ABC123",
      "maxLevel": 10,
      "totalScore": 15000,
      "friendshipCreatedAt": "2026-01-19T10:30:00Z"
    }
  ],
  "total": 1
}
```

### 2. 获取邀请统计

**接口**: `GET /api/users/:id/invite-stats`

**响应示例**:
```json
{
  "success": true,
  "inviteCount": 5,
  "dailyInviteCount": 1,
  "dailyInviteLimit": 2,
  "lastInviteDate": "2026-01-19"
}
```

### 3. 获取邀请码

**接口**: `GET /api/users/:id/invite-code`

**响应示例**:
```json
{
  "success": true,
  "inviteCode": "ABC123",
  "shareUrl": "https://game.example.com/register?invite=ABC123"
}
```

## 邀请机制

### 邀请码格式

- 长度:6位
- 字符:大写字母(A-Z)+ 数字(0-9)
- 示例:`ABC123`, `TY646U`

### 邀请码生成

**代码位置**: `src/storage/database/userManager.ts:10-18`

```typescript
private generateInviteCode(): string {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  let code = "";
  for (let i = 0; i < 6; i++) {
    code += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return code;
}
```

### 邀请奖励

注册时新用户获得:
1. 与邀请人的好友关系
2. 一张随机卡牌(或指定卡牌)
3. 卡牌来源标注为"好友 邀请人用户名"

邀请人获得:
1. 邀请计数+1
2. 好友关系

### 每日邀请限制

**限制逻辑**:
```typescript
// 检查是否超过每日邀请限制
const today = new Date().toISOString().split('T')[0];
const isSameDay = user.lastInviteDate === today;

if (isSameDay && user.dailyInviteCount >= user.dailyInviteLimit) {
  return { error: "今日邀请次数已达上限" };
}

// 更新每日邀请计数
await db.execute(sql`
  UPDATE users
  SET
    daily_invite_count = daily_invite_count + 1,
    last_invite_date = ${today},
    invite_count = invite_count + 1
  WHERE id = ${userId}
`);
```

**每日重置**:
- 当 `last_invite_date !== 今天` 时
- 重置 `daily_invite_count = 0`
- 更新 `last_invite_date = 今天`

## 关键代码位置

### 建立好友关系

**文件**: `src/app/api/auth/register/route.ts`

```typescript
// 建立双向好友关系(第65-71行)
await tx.insert(friendships).values({
  userId: user.id,
  friendId: inviter.id,
  status: "accepted",
});
await tx.insert(friendships).values({
  userId: inviter.id,
  friendId: user.id,
  status: "accepted",
});
```

### 查询用户好友

**文件**: 需要创建

```typescript
async function getUserFriends(userId: string) {
  const db = await getDb();
  const result = await db.execute(sql`
    SELECT u.*, f.created_at as friendship_created_at
    FROM users u
    JOIN friendships f ON u.id = f.friend_id
    WHERE f.user_id = ${userId} AND f.status = 'accepted'
    ORDER BY f.created_at DESC
  `);
  return result.rows;
}
```

### 验证邀请码

**文件**: `src/storage/database/userManager.ts:79-99`

```typescript
async getUserByInviteCode(inviteCode: string): Promise<User | null> {
  const db = await getDb();
  const result = await db.execute(
    sql`SELECT * FROM users WHERE invite_code = ${inviteCode} LIMIT 1`
  );
  if (!result.rows || result.rows.length === 0) {
    return null;
  }
  const dbUser = result.rows[0] as any;
  return this.mapDbUserToUser(dbUser);
}
```

## 数据一致性

### 事务保证

建立好友关系必须在事务中进行:

```typescript
await db.transaction(async (tx) => {
  // 创建用户
  const user = await userManager.register({...}, tx);
  // 建立双向好友关系
  await tx.insert(friendships).values({...});
  await tx.insert(friendships).values({...});
  // 赠送卡牌
  await tx.insert(userCards).values({...});
});
```

**好处**:
- 防止单向关系
- 确保好友关系完整
- 避免数据不一致

### 唯一性约束

- `(user_id, friend_id)` - 复合唯一约束
- 防止重复建立相同关系
- 数据库层面保证数据完整性

## 扩展功能建议

### 1. 好友推荐

基于以下条件推荐好友:
- 共同好友
- 相似游戏进度
- 地理位置相近

### 2. 好友排行榜

```json
{
  "friends_ranking": [
    {
      "username": "好友A",
      "totalScore": 15000,
      "rank": 1
    },
    {
      "username": "好友B",
      "totalScore": 12000,
      "rank": 2
    }
  ]
}
```

### 3. 好友互动

- 点赞好友动态
- 发送私信
- 赠送卡牌
- 组队挑战

### 4. 邀请活动

- 邀请排行榜
- 邀请奖励升级
- 特殊活动期间双倍奖励

### 5. 好友管理

- 删除好友(解除关系)
- 拉黑好友
- 好友分组

## 常见问题

### Q1: 为什么需要双向好友关系?

A: 好友关系应该是双向的,方便:
- 查询好友列表
- 统计好友数量
- 实现好友互动

### Q2: 如何删除好友?

A: 当前未实现,建议开发:
```typescript
// 删除双向关系
await db.delete(friendships)
  .where(or(
    and(eq(friendships.userId, userA), eq(friendships.friendId, userB)),
    and(eq(friendships.userId, userB), eq(friendships.friendId, userA))
  ));
```

### Q3: 邀请码会过期吗?

A: 当前不会过期,但可以增加:
- 有效期限制
- 使用次数限制
- 动态生成机制

### Q4: 如何查看我的邀请记录?

A: 需要开发邀请记录表:
```typescript
export const inviteRecords = pgTable("invite_records", {
  id: varchar("id", { length: 36 }).primaryKey(),
  inviterId: varchar("inviter_id", { length: 36 }).notNull(),
  inviteeId: varchar("invitee_id", { length: 36 }).notNull(),
  inviteCode: varchar("invite_code", { length: 20 }).notNull(),
  status: varchar("status", { length: 20 }).notNull(),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
});
```

## 性能优化

### 索引优化

当前索引:
- `(user_id, friend_id)` - 复合索引
- `(friend_id, user_id)` - 复合索引

**建议**:
- 添加 `status` 索引用于过滤
- 添加 `created_at` 索引用于排序

### 查询优化

**好友列表查询**:
```sql
-- 使用复合索引
SELECT u.*
FROM users u
JOIN friendships f ON u.id = f.friend_id
WHERE f.user_id = ? AND f.status = 'accepted'
ORDER BY f.created_at DESC
LIMIT 20
```

**缓存策略**:
- 好友列表缓存(5分钟)
- 邀请码缓存(永久)
- 邀请统计缓存(1分钟)

## 维护记录

- 2026-01-19: 添加 friendships 复合唯一约束
- 2026-01-19: 修复注册时好友关系双向建立逻辑
- 2026-01-19: 优化事务处理,确保数据一致性


收藏

扫描二维码,在手机上阅读
文章目录


    2026年01月25日 热搜榜单,一览天下事

    呆鸟修仙 占卜、预测、人缘系统

    评 论
    评论已关闭