2026-05-25 14:31:58 +08:00

162 lines
4.8 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 { NextRequest, NextResponse } from 'next/server';
import { getPublicProjectDb } from '@/lib/project-auth';
import { signLicense } from '@/lib/crypto';
import { generateLicenseConfig, LICENSE_CONFIGS } from '@/lib/types';
import { z } from 'zod';
const registerFreeSchema = z.object({
fingerprint: z.object({
machineId: z.string({ required_error: '系统信息不完整:machineId 缺失' }),
hostname: z.string({ required_error: '系统信息不完整:hostname 缺失' }),
macAddress: z.string({ required_error: '系统信息不完整:macAddress 缺失' }),
cpuId: z.string().optional(),
instanceId: z.string({ required_error: '系统信息不完整:instanceId 缺失' }),
}),
customerName: z.string().min(1, '客户名称不能为空'),
contactInfo: z.string().min(1, '联系方式不能为空'),
timestamp: z.number({ required_error: '请求时间戳缺失' }),
});
function generateFreeActivationCode(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const segments: string[] = [];
for (let i = 0; i < 4; i++) {
let segment = '';
for (let j = 0; j < 4; j++) {
segment += chars.charAt(Math.floor(Math.random() * chars.length));
}
segments.push(segment);
}
return `FLNXI-FREE-${segments.join('-')}`;
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ projectCode: string }> }
) {
try {
const { projectCode } = await params;
const projectDbResult = await getPublicProjectDb(projectCode);
if (!projectDbResult.success) {
return projectDbResult.response;
}
const body = await request.json().catch(() => ({}));
const parsed = registerFreeSchema.safeParse(body);
if (!parsed.success) {
const firstError = parsed.error.errors[0]?.message || '参数验证失败';
return NextResponse.json(
{ success: false, error: firstError },
{ status: 400 }
);
}
const { fingerprint, customerName, contactInfo, timestamp } = parsed.data;
const now = Date.now();
if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
return NextResponse.json(
{ success: false, error: '请求已过期' },
{ status: 400 }
);
}
const existingActivation = await projectDbResult.prisma.activation.findFirst({
where: {
instanceId: fingerprint.instanceId,
isActive: true,
},
include: {
license: true,
},
});
if (existingActivation) {
const signedLicense = signLicense(
generateLicenseConfig(
existingActivation.license.licenseType,
existingActivation.license.expiresAt
)
);
return NextResponse.json({
success: true,
license: signedLicense,
data: {
activationCode: existingActivation.license.licenseKey,
plan: existingActivation.license.licenseType,
planName:
existingActivation.license.licenseType === 'FREE'
? '免费版'
: existingActivation.license.licenseType === 'STANDARD'
? '标准版'
: '专业版',
isExisting: true,
},
});
}
const activationCode = generateFreeActivationCode();
const clientIP =
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
request.headers.get('x-real-ip') ||
'unknown';
await projectDbResult.prisma.$transaction(async (tx) => {
const license = await tx.license.create({
data: {
licenseKey: activationCode,
licenseType: 'FREE',
customerName,
contactInfo,
maxUsers: LICENSE_CONFIGS.FREE.limits.maxUsers,
maxMaterials: LICENSE_CONFIGS.FREE.limits.maxMaterials,
features: LICENSE_CONFIGS.FREE.features,
expiresAt: null,
},
});
await tx.activation.create({
data: {
licenseId: license.id,
instanceId: fingerprint.instanceId,
fingerprint: fingerprint as object,
lastHeartbeat: new Date(),
},
});
await tx.heartbeatLog.create({
data: {
licenseId: license.id,
instanceId: fingerprint.instanceId,
fingerprintHash: fingerprint.machineId,
ipAddress: clientIP,
},
});
});
const signedLicense = signLicense(generateLicenseConfig('FREE', null));
return NextResponse.json({
success: true,
license: signedLicense,
data: {
activationCode,
plan: 'FREE',
planName: '免费版',
isExisting: false,
},
});
} catch (error) {
console.error('项目免费版注册失败:', error);
return NextResponse.json(
{ success: false, error: '服务器内部错误' },
{ status: 500 }
);
}
}