2026-03-03 16:57:49 +08:00
|
|
|
|
const pool = require('../config/db');
|
|
|
|
|
|
|
|
|
|
|
|
const UserModel = {
|
|
|
|
|
|
// 通过openid查找或创建用户
|
|
|
|
|
|
async findOrCreate(openid, userInfo = {}) {
|
|
|
|
|
|
const [existing] = await pool.query(
|
|
|
|
|
|
'SELECT * FROM users WHERE openid = ?',
|
|
|
|
|
|
[openid]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existing.length > 0) {
|
|
|
|
|
|
return existing[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新用户
|
|
|
|
|
|
const [result] = await pool.query(
|
|
|
|
|
|
'INSERT INTO users (openid, nickname, avatar_url, gender) VALUES (?, ?, ?, ?)',
|
|
|
|
|
|
[openid, userInfo.nickname || '', userInfo.avatarUrl || '', userInfo.gender || 0]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: result.insertId,
|
|
|
|
|
|
openid,
|
|
|
|
|
|
nickname: userInfo.nickname || '',
|
|
|
|
|
|
avatar_url: userInfo.avatarUrl || '',
|
|
|
|
|
|
gender: userInfo.gender || 0
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 更新用户信息
|
|
|
|
|
|
async updateProfile(userId, userInfo) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'UPDATE users SET nickname = ?, avatar_url = ?, gender = ? WHERE id = ?',
|
|
|
|
|
|
[userInfo.nickname, userInfo.avatarUrl, userInfo.gender, userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户进度
|
|
|
|
|
|
async getProgress(userId, storyId = null) {
|
|
|
|
|
|
let sql = `SELECT up.*, s.title as story_title, s.cover_url
|
|
|
|
|
|
FROM user_progress up
|
|
|
|
|
|
JOIN stories s ON up.story_id = s.id
|
|
|
|
|
|
WHERE up.user_id = ?`;
|
|
|
|
|
|
const params = [userId];
|
|
|
|
|
|
|
|
|
|
|
|
if (storyId) {
|
|
|
|
|
|
sql += ' AND up.story_id = ?';
|
|
|
|
|
|
params.push(storyId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sql += ' ORDER BY up.updated_at DESC';
|
|
|
|
|
|
const [rows] = await pool.query(sql, params);
|
|
|
|
|
|
return storyId ? rows[0] : rows;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 保存用户进度
|
|
|
|
|
|
async saveProgress(userId, storyId, data) {
|
|
|
|
|
|
const { currentNodeKey, isCompleted, endingReached } = data;
|
|
|
|
|
|
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO user_progress (user_id, story_id, current_node_key, is_completed, ending_reached)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?)
|
|
|
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
|
|
current_node_key = VALUES(current_node_key),
|
|
|
|
|
|
is_completed = VALUES(is_completed),
|
|
|
|
|
|
ending_reached = VALUES(ending_reached),
|
|
|
|
|
|
play_count = play_count + 1`,
|
|
|
|
|
|
[userId, storyId, currentNodeKey, isCompleted || false, endingReached || '']
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果完成了,记录结局
|
|
|
|
|
|
if (isCompleted && endingReached) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT IGNORE INTO user_endings (user_id, story_id, ending_name) VALUES (?, ?, ?)`,
|
|
|
|
|
|
[userId, storyId, endingReached]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新用户统计
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'UPDATE users SET total_play_count = total_play_count + 1, total_endings = (SELECT COUNT(*) FROM user_endings WHERE user_id = ?) WHERE id = ?',
|
|
|
|
|
|
[userId, userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 点赞/取消点赞
|
|
|
|
|
|
async toggleLike(userId, storyId, isLiked) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO user_progress (user_id, story_id, is_liked)
|
|
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
|
|
ON DUPLICATE KEY UPDATE is_liked = ?`,
|
|
|
|
|
|
[userId, storyId, isLiked, isLiked]
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 收藏/取消收藏
|
|
|
|
|
|
async toggleCollect(userId, storyId, isCollected) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO user_progress (user_id, story_id, is_collected)
|
|
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
|
|
ON DUPLICATE KEY UPDATE is_collected = ?`,
|
|
|
|
|
|
[userId, storyId, isCollected, isCollected]
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取收藏列表
|
|
|
|
|
|
async getCollections(userId) {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT s.id, s.title, s.cover_url, s.description, s.category, s.play_count, s.like_count
|
|
|
|
|
|
FROM user_progress up
|
|
|
|
|
|
JOIN stories s ON up.story_id = s.id
|
|
|
|
|
|
WHERE up.user_id = ? AND up.is_collected = 1
|
|
|
|
|
|
ORDER BY up.updated_at DESC`,
|
|
|
|
|
|
[userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户解锁的结局
|
|
|
|
|
|
async getUnlockedEndings(userId, storyId = null) {
|
|
|
|
|
|
let sql = 'SELECT * FROM user_endings WHERE user_id = ?';
|
|
|
|
|
|
const params = [userId];
|
|
|
|
|
|
|
|
|
|
|
|
if (storyId) {
|
|
|
|
|
|
sql += ' AND story_id = ?';
|
|
|
|
|
|
params.push(storyId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const [rows] = await pool.query(sql, params);
|
|
|
|
|
|
return rows;
|
2026-03-03 17:24:22 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取我的作品
|
|
|
|
|
|
async getMyWorks(userId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT id, title, description, category, cover_url, play_count, like_count,
|
|
|
|
|
|
comment_count, status, created_at, updated_at,
|
|
|
|
|
|
COALESCE(earnings, 0) as earnings
|
|
|
|
|
|
FROM stories
|
|
|
|
|
|
WHERE author_id = ?
|
|
|
|
|
|
ORDER BY created_at DESC`,
|
|
|
|
|
|
[userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 表可能还没有author_id字段,返回空数组
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取草稿箱
|
|
|
|
|
|
async getDrafts(userId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT id, title, category, node_count, source, created_at, updated_at
|
|
|
|
|
|
FROM story_drafts
|
|
|
|
|
|
WHERE user_id = ?
|
|
|
|
|
|
ORDER BY updated_at DESC`,
|
|
|
|
|
|
[userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 表可能不存在,返回空数组
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最近游玩
|
|
|
|
|
|
async getRecentPlayed(userId, limit = 10) {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT s.id, s.title, s.category, s.description, s.cover_url,
|
|
|
|
|
|
up.current_node_key, up.is_completed,
|
|
|
|
|
|
CASE WHEN up.is_completed THEN '已完成' ELSE '进行中' END as progress
|
|
|
|
|
|
FROM user_progress up
|
|
|
|
|
|
JOIN stories s ON up.story_id = s.id
|
|
|
|
|
|
WHERE up.user_id = ?
|
|
|
|
|
|
ORDER BY up.updated_at DESC
|
|
|
|
|
|
LIMIT ?`,
|
|
|
|
|
|
[userId, limit]
|
|
|
|
|
|
);
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取AI创作历史
|
|
|
|
|
|
async getAIHistory(userId, limit = 20) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT id, gen_type, input_prompt, output_content, status, created_at
|
|
|
|
|
|
FROM ai_generations
|
|
|
|
|
|
WHERE user_id = ?
|
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
|
LIMIT ?`,
|
|
|
|
|
|
[userId, limit]
|
|
|
|
|
|
);
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取AI配额
|
|
|
|
|
|
async getAIQuota(userId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
|
`SELECT daily_free_total as daily, daily_free_used as used,
|
|
|
|
|
|
purchased_quota as purchased, gift_quota as gift
|
|
|
|
|
|
FROM user_ai_quota
|
|
|
|
|
|
WHERE user_id = ?`,
|
|
|
|
|
|
[userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
if (rows.length > 0) {
|
|
|
|
|
|
return rows[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
// 没有记录则返回默认值
|
|
|
|
|
|
return { daily: 3, used: 0, purchased: 0, gift: 0 };
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return { daily: 3, used: 0, purchased: 0, gift: 0 };
|
|
|
|
|
|
}
|
2026-03-03 16:57:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = UserModel;
|