# 呆鸟修仙 好友系统

本文档详细说明好友系统的功能、数据逻辑和关键代码。
## 功能概述
好友系统支持用户通过邀请码注册自动建立好友关系,实现社交功能。关系为双向,支持邀请计数和奖励机制。
## 系统架构
### 数据模型
```
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: 优化事务处理,确保数据一致性
