162 lines
4.8 KiB
TypeScript
162 lines
4.8 KiB
TypeScript
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 }
|
||
);
|
||
}
|
||
}
|