chore: mqsrv backend
This commit is contained in:
279
prisma/schema.prisma
Normal file
279
prisma/schema.prisma
Normal file
@@ -0,0 +1,279 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 用户表
|
||||
// ============================================
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique @db.VarChar(50)
|
||||
email String @unique @db.VarChar(100)
|
||||
passwordHash String @map("password_hash") @db.VarChar(255)
|
||||
avatar String? @db.VarChar(255)
|
||||
|
||||
// VIP 状态
|
||||
isVip Boolean @default(false) @map("is_vip")
|
||||
vipLevel Int @default(0) @map("vip_level")
|
||||
vipExpireAt DateTime? @map("vip_expire_at")
|
||||
|
||||
// 统计
|
||||
schemesCount Int @default(0) @map("schemes_count")
|
||||
favoritesCount Int @default(0) @map("favorites_count")
|
||||
|
||||
// 设备绑定
|
||||
installId String? @map("install_id") @db.VarChar(64)
|
||||
deviceHash String? @map("device_hash") @db.VarChar(64)
|
||||
lastDeviceCheck DateTime? @map("last_device_check")
|
||||
|
||||
// 元数据
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
lastLoginAt DateTime? @map("last_login_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
// 关系
|
||||
schemes Scheme[]
|
||||
schemesAob SchemeAob[]
|
||||
filterShares FilterShare[]
|
||||
favorites Favorite[]
|
||||
likes Like[]
|
||||
logs UserLog[]
|
||||
usedVipCards VipCard[] @relation("UsedCards")
|
||||
deviceBindings DeviceBinding[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// VIP 卡密表
|
||||
// ============================================
|
||||
model VipCard {
|
||||
id String @id @default(uuid())
|
||||
cardKey String @unique @map("card_key") @db.VarChar(50)
|
||||
|
||||
cardType String @map("card_type") @db.VarChar(20)
|
||||
days Int
|
||||
|
||||
status String @default("UNUSED") @db.VarChar(20)
|
||||
usedBy String? @map("used_by")
|
||||
usedAt DateTime? @map("used_at")
|
||||
|
||||
batchId String? @map("batch_id")
|
||||
generatedAt DateTime @default(now()) @map("generated_at")
|
||||
|
||||
originalPrice Decimal? @map("original_price") @db.Decimal(10, 2)
|
||||
salePrice Decimal? @map("sale_price") @db.Decimal(10, 2)
|
||||
|
||||
user User? @relation("UsedCards", fields: [usedBy], references: [id], onDelete: SetNull)
|
||||
|
||||
@@map("vip_cards")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 方案表 - 烽火地带
|
||||
// ============================================
|
||||
model Scheme {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
|
||||
title String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
weaponName String? @map("weapon_name") @db.VarChar(100)
|
||||
category String? @db.VarChar(50)
|
||||
|
||||
schemeContent String @map("scheme_content") @db.Text
|
||||
contentEncrypted Boolean @default(true) @map("content_encrypted")
|
||||
|
||||
price Int @default(0)
|
||||
|
||||
viewsCount Int @default(0) @map("views_count")
|
||||
downloadsCount Int @default(0) @map("downloads_count")
|
||||
likesCount Int @default(0) @map("likes_count")
|
||||
favoritesCount Int @default(0) @map("favorites_count")
|
||||
|
||||
gpuModel String? @map("gpu_model") @db.VarChar(100)
|
||||
driverVersion String? @map("driver_version") @db.VarChar(50)
|
||||
appVersion String? @map("app_version") @db.VarChar(20)
|
||||
|
||||
status String @default("DRAFT") @db.VarChar(20)
|
||||
isOfficial Boolean @default(false) @map("is_official")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([weaponName])
|
||||
@@index([category])
|
||||
@@index([status])
|
||||
@@map("schemes")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 方案表 - 全面战场 (AOB)
|
||||
// ============================================
|
||||
model SchemeAob {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
|
||||
title String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
weaponName String? @map("weapon_name") @db.VarChar(100)
|
||||
category String? @db.VarChar(50)
|
||||
|
||||
schemeContent String @map("scheme_content") @db.Text
|
||||
contentEncrypted Boolean @default(true) @map("content_encrypted")
|
||||
|
||||
price Int @default(0)
|
||||
|
||||
viewsCount Int @default(0) @map("views_count")
|
||||
downloadsCount Int @default(0) @map("downloads_count")
|
||||
likesCount Int @default(0) @map("likes_count")
|
||||
favoritesCount Int @default(0) @map("favorites_count")
|
||||
|
||||
status String @default("DRAFT") @db.VarChar(20)
|
||||
isOfficial Boolean @default(false) @map("is_official")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([weaponName])
|
||||
@@map("schemes_aob")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 滤镜分享表
|
||||
// ============================================
|
||||
model FilterShare {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
|
||||
title String @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
category String? @db.VarChar(50)
|
||||
|
||||
filterContent String @map("filter_content") @db.Text
|
||||
contentFormat String @default("MQTS1") @map("content_format") @db.VarChar(20)
|
||||
|
||||
viewsCount Int @default(0) @map("views_count")
|
||||
likesCount Int @default(0) @map("likes_count")
|
||||
|
||||
status String @default("DRAFT") @db.VarChar(20)
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@map("filter_shares")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 收藏表
|
||||
// ============================================
|
||||
model Favorite {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
targetType String @map("target_type") @db.VarChar(20)
|
||||
targetId String @map("target_id")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, targetType, targetId])
|
||||
@@index([userId])
|
||||
@@map("favorites")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 点赞表
|
||||
// ============================================
|
||||
model Like {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
targetType String @map("target_type") @db.VarChar(20)
|
||||
targetId String @map("target_id")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, targetType, targetId])
|
||||
@@index([userId])
|
||||
@@index([targetType, targetId])
|
||||
@@map("likes")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 分类表
|
||||
// ============================================
|
||||
model Category {
|
||||
id String @id @default(uuid())
|
||||
name String @db.VarChar(50)
|
||||
type String @db.VarChar(20)
|
||||
|
||||
sortOrder Int @default(0) @map("sort_order")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("categories")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 用户日志表
|
||||
// ============================================
|
||||
model UserLog {
|
||||
id String @id @default(uuid())
|
||||
userId String? @map("user_id")
|
||||
|
||||
action String @db.VarChar(50)
|
||||
targetType String? @map("target_type") @db.VarChar(50)
|
||||
targetId String? @map("target_id")
|
||||
|
||||
installId String? @map("install_id") @db.VarChar(64)
|
||||
deviceHash String? @map("device_hash") @db.VarChar(64)
|
||||
ipAddress String? @map("ip_address") @db.VarChar(45)
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([userId])
|
||||
@@index([action])
|
||||
@@map("user_logs")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 设备绑定表
|
||||
// ============================================
|
||||
model DeviceBinding {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
installId String @map("install_id") @db.VarChar(64)
|
||||
deviceHash String @map("device_hash") @db.VarChar(64)
|
||||
|
||||
boundAt DateTime @default(now()) @map("bound_at")
|
||||
lastActiveAt DateTime? @map("last_active_at")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, installId])
|
||||
@@map("device_bindings")
|
||||
}
|
||||
90
prisma/seed.ts
Normal file
90
prisma/seed.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import crypto from 'crypto';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* 生成 VIP 卡密
|
||||
*/
|
||||
function generateCardKey(days: number): string {
|
||||
const prefix = `VIP${days}`;
|
||||
const segments = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
segments.push(crypto.randomBytes(2).toString('hex').toUpperCase());
|
||||
}
|
||||
return `${prefix}-${segments.join('-')}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 开始种子数据初始化...');
|
||||
|
||||
// ============================================
|
||||
// 创建分类
|
||||
// ============================================
|
||||
const categories = [
|
||||
// 烽火地带
|
||||
{ name: '突击步枪', type: 'SCHEME' },
|
||||
{ name: '冲锋枪', type: 'SCHEME' },
|
||||
{ name: '狙击步枪', type: 'SCHEME' },
|
||||
{ name: '轻机枪', type: 'SCHEME' },
|
||||
{ name: '霰弹枪', type: 'SCHEME' },
|
||||
{ name: '手枪', type: 'SCHEME' },
|
||||
|
||||
// 全面战场
|
||||
{ name: '突击步枪', type: 'SCHEME_AOB' },
|
||||
{ name: '冲锋枪', type: 'SCHEME_AOB' },
|
||||
{ name: '狙击步枪', type: 'SCHEME_AOB' },
|
||||
{ name: '轻机枪', type: 'SCHEME_AOB' },
|
||||
{ name: '霰弹枪', type: 'SCHEME_AOB' },
|
||||
|
||||
// 滤镜
|
||||
{ name: '烽火地带', type: 'FILTER' },
|
||||
{ name: '全面战场', type: 'FILTER' },
|
||||
{ name: '通用', type: 'FILTER' },
|
||||
];
|
||||
|
||||
for (const cat of categories) {
|
||||
await prisma.category.create({
|
||||
data: cat,
|
||||
}).catch(() => {}); // 忽略重复错误
|
||||
}
|
||||
console.log(`✅ 创建 ${categories.length} 个分类`);
|
||||
|
||||
// ============================================
|
||||
// 生成 VIP 卡密
|
||||
// ============================================
|
||||
const cardConfigs = [
|
||||
{ type: 'MONTH', days: 30, count: 100 },
|
||||
{ type: 'QUARTER', days: 90, count: 50 },
|
||||
{ type: 'YEAR', days: 365, count: 20 },
|
||||
];
|
||||
|
||||
let totalCards = 0;
|
||||
for (const config of cardConfigs) {
|
||||
const cards = [];
|
||||
for (let i = 0; i < config.count; i++) {
|
||||
cards.push({
|
||||
cardKey: generateCardKey(config.days),
|
||||
cardType: config.type,
|
||||
days: config.days,
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.vipCard.createMany({ data: cards, skipDuplicates: true });
|
||||
totalCards += config.count;
|
||||
console.log(`✅ 生成 ${config.type} 卡密 ${config.count} 张`);
|
||||
}
|
||||
|
||||
console.log(`\n🎉 种子数据初始化完成!`);
|
||||
console.log(` - 分类:${categories.length} 个`);
|
||||
console.log(` - VIP 卡密:${totalCards} 张`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('❌ 种子数据初始化失败:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user