543 lines
14 KiB
TypeScript
543 lines
14 KiB
TypeScript
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;
|
||
}
|
||
}
|