Files
ai/backend/src/controllers/accountController.ts
2025-07-08 23:42:53 +08:00

543 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Request, Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest } from '../middleware/authMiddleware';
export const accountController = {
// 获取所有账号 (管理员)
async getAllAccounts(req: Request, res: Response) {
const accounts = await prisma.websiteAccount.findMany({
include: {
accountAssignments: {
include: {
user: {
select: {
id: true,
username: true,
}
}
}
}
}
});
return res.json({
accounts: accounts.map((account: any) => ({
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt,
assignedUsers: account.accountAssignments.map((aa: any) => aa.user)
}))
});
},
// 根据ID获取账号 (管理员)
async getAccountById(req: Request, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id },
include: {
accountAssignments: {
include: {
user: {
select: {
id: true,
username: true,
}
}
}
}
}
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
return res.json({
account: {
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt,
assignedUsers: account.accountAssignments.map((aa: any) => aa.user)
}
});
},
// 创建新账号 (管理员)
async createAccount(req: Request, res: Response) {
const { website, accountName, token, isActive, maxUsers } = req.body;
if (!website || !accountName || !token) {
return res.status(400).json({ error: '网站、账号名称和token是必需的' });
}
// 检查账号是否已存在
const existingAccount = await prisma.websiteAccount.findFirst({
where: {
website,
accountName
}
});
if (existingAccount) {
return res.status(400).json({ error: '账号已存在' });
}
const account = await prisma.websiteAccount.create({
data: {
website,
accountName,
token,
isActive: isActive !== undefined ? isActive : true,
maxUsers: maxUsers || 1,
currentUsers: 0,
}
});
return res.status(201).json({
message: '账号创建成功',
account: {
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt
}
});
},
// 更新账号 (管理员)
async updateAccount(req: Request, res: Response) {
const { id } = req.params;
const { website, accountName, token, maxUsers, isActive } = req.body;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查是否与其他账号重复(排除当前账号)
if (website && accountName) {
const existingAccount = await prisma.websiteAccount.findFirst({
where: {
website,
accountName,
id: { not: id }
}
});
if (existingAccount) {
return res.status(400).json({ error: '该网站和账号名称组合已存在' });
}
}
const updatedAccount = await prisma.websiteAccount.update({
where: { id },
data: {
website,
accountName,
token,
maxUsers,
isActive,
}
});
return res.json({
message: '账号更新成功',
account: {
id: updatedAccount.id,
website: updatedAccount.website,
username: updatedAccount.accountName,
token: updatedAccount.token,
isActive: updatedAccount.isActive,
createdAt: updatedAccount.createdAt,
updatedAt: updatedAccount.updatedAt
}
});
},
// 删除账号 (管理员)
async deleteAccount(req: Request, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 删除账号相关的所有分配
await prisma.$transaction([
prisma.accountAssignment.deleteMany({ where: { accountId: id } }),
prisma.websiteAccount.delete({ where: { id } })
]);
return res.json({ message: '账号删除成功' });
},
// 获取用户的已分配账号
async getUserAccounts(req: AuthRequest, res: Response) {
if (!req.user) {
return res.status(401).json({ error: '未授权' });
}
const assignments = await prisma.accountAssignment.findMany({
where: {
userId: req.user.id,
isActive: true,
OR: [
{ expiresAt: null },
{ expiresAt: { gt: new Date() } }
]
},
include: {
account: {
select: {
id: true,
website: true,
accountName: true,
token: true,
isActive: true,
createdAt: true,
updatedAt: true,
}
}
}
});
return res.json({
accounts: assignments.map((aa: any) => ({
id: aa.account.id,
website: aa.account.website,
username: aa.account.accountName,
token: aa.account.token,
isActive: aa.account.isActive,
createdAt: aa.account.createdAt,
updatedAt: aa.account.updatedAt,
assignedAt: aa.assignedAt,
expiresAt: aa.expiresAt,
}))
});
},
// 分配账号给用户 (管理员)
async assignAccount(req: Request, res: Response) {
const { id } = req.params;
const { userId, expiresAt } = req.body;
if (!id || !userId) {
return res.status(400).json({ error: '账号ID和用户ID都是必需的' });
}
// 检查账号是否存在
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查用户是否存在
const user = await prisma.user.findUnique({
where: { id: userId }
});
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
// 检查账号是否已满
if (account.currentUsers >= account.maxUsers) {
return res.status(400).json({ error: '账号已达到最大用户数' });
}
// 检查是否已经分配
const existingAssignment = await prisma.accountAssignment.findFirst({
where: {
userId,
accountId: id,
isActive: true
}
});
if (existingAssignment) {
return res.status(400).json({ error: '用户已分配此账号' });
}
// 创建分配
const assignment = await prisma.accountAssignment.create({
data: {
userId,
accountId: id,
expiresAt: expiresAt ? new Date(expiresAt) : null,
isActive: true,
}
});
// 更新账号当前用户数
await prisma.websiteAccount.update({
where: { id },
data: {
currentUsers: {
increment: 1
}
}
});
return res.json({
message: '账号分配成功',
assignment
});
},
// 取消账号分配 (管理员)
async unassignAccount(req: Request, res: Response) {
const { id, userId } = req.params;
if (!id || !userId) {
return res.status(400).json({ error: '账号ID和用户ID都是必需的' });
}
const assignment = await prisma.accountAssignment.findFirst({
where: {
userId,
accountId: id,
isActive: true
}
});
if (!assignment) {
return res.status(404).json({ error: '分配不存在' });
}
// 删除分配
await prisma.accountAssignment.delete({
where: { id: assignment.id }
});
// 更新账号当前用户数
await prisma.websiteAccount.update({
where: { id },
data: {
currentUsers: {
decrement: 1
}
}
});
return res.json({ message: '账号分配已取消' });
},
// 网站登录
async loginToWebsite(req: Request, res: Response) {
const { accountId } = req.params;
const { userId } = req.body;
if (!accountId || !userId) {
return res.status(400).json({ error: '账号ID和用户ID是必需的' });
}
try {
// 获取账号信息
const account = await prisma.websiteAccount.findUnique({
where: { id: accountId }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查用户是否有权限访问该账号
console.log('检查用户权限:', { accountId, userId });
const assignment = await prisma.accountAssignment.findFirst({
where: {
accountId,
userId,
isActive: true,
OR: [
{ expiresAt: null },
{ expiresAt: { gt: new Date() } }
]
}
});
console.log('权限检查结果:', {
assignment: assignment ? 'found' : 'not found',
accountId,
userId,
currentTime: new Date()
});
if (!assignment) {
// 获取更多调试信息
const allAssignments = await prisma.accountAssignment.findMany({
where: { accountId, userId }
});
console.log('该用户的所有分配记录:', allAssignments);
return res.status(403).json({ error: '您没有权限访问该账号' });
}
let loginUrl = '';
// 根据网站类型处理登录
switch (account.website) {
case 'claude':
loginUrl = await handleClaudeLogin(account.token, userId);
break;
case 'chatgpt':
loginUrl = await handleChatGPTLogin(account.token, userId);
break;
case 'grok':
loginUrl = await handleGrokLogin(account.token, userId);
break;
default:
return res.status(400).json({ error: '不支持的网站类型' });
}
return res.json({
success: true,
loginUrl,
website: account.website,
accountName: account.accountName
});
} catch (error) {
console.error('网站登录失败:', error);
return res.status(500).json({ error: '登录失败,请稍后重试' });
}
}
};
// Claude 登录处理
async function handleClaudeLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.CLAUDE_TARGET_URL || 'https://chat.micar9.com';
console.log('Claude登录处理:', { token, userName });
// 第一步获取oauth token
const oauthResponse = await fetch(`${baseUrl}/manage-api/auth/oauth_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_key: token,
unique_name: userName
})
});
if (!oauthResponse.ok) {
throw new Error(`OAuth token 请求失败: ${oauthResponse.status}`);
}
const oauthData = await oauthResponse.json() as { login_url?: string };
if (!oauthData.login_url) {
throw new Error('未获取到登录URL');
}
return oauthData.login_url;
} catch (error) {
console.error('Claude登录处理失败:', error);
throw error;
}
}
// ChatGPT 登录处理
async function handleChatGPTLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.CHATGPT_TARGET_URL || 'http://127.0.0.1:8181';
const response = await fetch(`${baseUrl}/api/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ADMIN_PASSWORD || 'admin'}`
},
body: JSON.stringify({
access_token: token,
user_name: userName,
isolated_session: true,
limits: []
})
});
if (!response.ok) {
throw new Error(`ChatGPT登录请求失败: ${response.status}`);
}
const data = await response.json() as { login_url?: string };
if (!data.login_url) {
throw new Error('未获取到登录URL');
}
return data.login_url;
} catch (error) {
console.error('ChatGPT登录处理失败:', error);
throw error;
}
}
// Grok 登录处理
async function handleGrokLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.GROK_TARGET_URL || 'https://grok-mirror.micar9.com';
const response = await fetch(`${baseUrl}/api/login-v2`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_name: userName,
sso_token: token
})
});
if (!response.ok) {
throw new Error(`Grok登录请求失败: ${response.status}`);
}
const data = await response.json() as { login_url?: string };
if (!data.login_url) {
throw new Error('未获取到登录URL');
}
return data.login_url;
} catch (error) {
console.error('Grok登录处理失败:', error);
throw error;
}
}