Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified backend/prisma/prisma/dev.db
Binary file not shown.
183 changes: 141 additions & 42 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,15 @@ datasource db {
url = "file:./prisma/dev.db"
}

enum Role {
STUDENT
LIBRARIAN
ADMIN
}

enum HoldStatus {
WAITING
READY
CANCELLED
}

enum CopyStatus {
AVAILABLE
BORROWED
LOST
DAMAGED
}

<<<<<<< HEAD
model User {
id Int @id @default(autoincrement())
name String
email String @unique
passwordHash String
studentId String? @unique
employeeId String? @unique // 馆员工号(你的功能需要)
role Role @default(STUDENT)
employeeId String? @unique
role String @default("STUDENT")
isBlocked Boolean @default(false)
blockReason String?
blockedAt DateTime?
Expand All @@ -46,9 +28,10 @@ model User {
auditLogs AuditLog[]
dueReminderLogs DueReminderLog[]
announcementPublishers AnnouncementPublisher[]
sentMessages Message[] @relation("MessageSender") // 站内信(你的功能需要)
receivedMessages Message[] @relation("MessageReceiver") // 站内信(你的功能需要)
sentMessages Message[] @relation("MessageSender")
receivedMessages Message[] @relation("MessageReceiver")
reminderLogs ReminderLog[]
reservations Reservation[]
}

model Book {
Expand All @@ -69,81 +52,168 @@ model Book {
}

model Copy {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
bookId Int
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
barcode String @unique
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
barcode String @unique
floor Int?
libraryArea String?
shelfNo String?
shelfLevel Int?
status CopyStatus @default(AVAILABLE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status String @default("AVAILABLE") // AVAILABLE, BORROWED, LOST, DAMAGED, RESERVED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
loans Loan[]
reservations Reservation[]
=======
enum Role {
STUDENT
LIBRARIAN
ADMIN
}

enum HoldStatus {
WAITING
READY
CANCELLED
}

model User {
id Int @id @default(autoincrement())
name String
email String @unique
passwordHash String
studentId String? @unique
role Role @default(STUDENT)
createdAt DateTime @default(now())
loans Loan[]
ratings Rating[]
holds Hold[]
wishlists Wishlist[]
auditLogs AuditLog[]
}

model Book {
id Int @id @default(autoincrement())
title String
author String
isbn String @unique
genre String
description String?
language String @default("English")
shelfLocation String?
available Boolean @default(true) // 保留兼容
totalCopies Int @default(1) // 新增
availableCopies Int @default(1) // 新增
createdAt DateTime @default(now())
loans Loan[]
ratings Rating[]
holds Hold[]
wishlists Wishlist[]
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
}

model Loan {
id Int @id @default(autoincrement())
<<<<<<< HEAD
copyId Int
userId Int
barcode String @unique // 借阅条形码 BC-xxxxxx-xxx
barcode String @unique
checkoutDate DateTime @default(now())
dueDate DateTime
returnDate DateTime?
fineAmount Float @default(0)
finePaid Boolean @default(false)
fineForgiven Boolean @default(false)
renewCount Int @default(0) // 续借次数(你的功能需要)
renewCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
copy Copy @relation(fields: [copyId], references: [id])
user User @relation(fields: [userId], references: [id])
reminderLogs ReminderLog[]
dueReminderLogs DueReminderLog[]
=======
bookId Int
userId Int
checkoutDate DateTime @default(now())
dueDate DateTime
returnDate DateTime?
fineAmount Float? @default(0)
finePaid Boolean? @default(false)
fineForgiven Boolean? @default(false)
renewCount Int @default(0)
createdAt DateTime @default(now())
book Book @relation(fields: [bookId], references: [id])
user User @relation(fields: [userId], references: [id])
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
}

model Rating {
id Int @id @default(autoincrement())
bookId Int
userId Int
stars Int @default(1)
review String? // 评论内容(你的功能需要)
<<<<<<< HEAD
review String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // 评论更新时间(你的功能需要)
updatedAt DateTime @updatedAt
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
=======
createdAt DateTime @default(now())
book Book @relation(fields: [bookId], references: [id])
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
user User @relation(fields: [userId], references: [id])

@@unique([bookId, userId])
}

model Hold {
<<<<<<< HEAD
id Int @id @default(autoincrement())
bookId Int
userId Int
status String @default("WAITING") // WAITING, READY, CANCELLED
createdAt DateTime @default(now())
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
=======
id Int @id @default(autoincrement())
bookId Int
userId Int
status HoldStatus @default(WAITING)
createdAt DateTime @default(now())
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
book Book @relation(fields: [bookId], references: [id])
user User @relation(fields: [userId], references: [id])
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
}

model Wishlist {
id Int @id @default(autoincrement())
bookId Int
userId Int
createdAt DateTime @default(now())
<<<<<<< HEAD
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
=======
book Book @relation(fields: [bookId], references: [id])
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
user User @relation(fields: [userId], references: [id])

@@unique([bookId, userId])
}

model Config {
<<<<<<< HEAD
id Int @id @default(autoincrement())
key String @unique
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
=======
id Int @id @default(autoincrement())
key String @unique
value String
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
}

model AuditLog {
Expand All @@ -156,6 +226,7 @@ model AuditLog {
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id])
}
<<<<<<< HEAD

model Announcement {
id Int @id @default(autoincrement())
Expand Down Expand Up @@ -196,24 +267,23 @@ model BackupLog {
filename String
filepath String
sizeBytes BigInt
status String @default("completed") // completed, failed, restored
type String @default("manual") // manual, scheduled
status String @default("completed")
type String @default("manual")
note String?
createdAt DateTime @default(now())
restoredAt DateTime?
createdBy Int? // 管理员 userId
createdBy Int?
}

// 图书到期提醒日志模型
model ReminderLog {
id Int @id @default(autoincrement())
userId Int
loanId Int
bookTitle String // 图书名称
dueDate DateTime // 图书到期日期
sendStatus String @default("success") // success, failed
bookTitle String
dueDate DateTime
sendStatus String @default("success")
sendTime DateTime @default(now())
errorMessage String? // 如果发送失败,记录错误信息
errorMessage String?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
loan Loan @relation(fields: [loanId], references: [id], onDelete: Cascade)
Expand All @@ -232,4 +302,33 @@ model DueReminderLog {
loan Loan @relation(fields: [loanId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
book Book @relation(fields: [bookId], references: [id])
}

// 新增:预约模型
model Reservation {
id Int @id @default(autoincrement())
copyId Int
userId Int
status String @default("PENDING") // PENDING, CONFIRMED, EXPIRED, CANCELLED
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

copy Copy @relation(fields: [copyId], references: [id])
user User @relation(fields: [userId], references: [id])

@@index([copyId])
@@index([userId])
@@index([status, expiresAt])
=======
model Librarian {
id Int @id @default(autoincrement())
employeeId String @unique @map("employee_id")
name String
password String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

@@map("librarians")
>>>>>>> af9ecfbeebfa89b807d4957f9b88257908c13b6b
}
19 changes: 17 additions & 2 deletions backend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ const blocklistRouter = require("./routes/blocklist");
const remindersRouter = require('./routes/reminders'); // 图书到期提醒路由
const backupService = require("./services/backup");
const { runDueReminderJob, getNextDueReminderTime } = require('./services/dueReminder');
const { expireReservations } = require('./services/expireReservations'); // 新增:预约过期清理

const app = express();
const port = Number(process.env.PORT) || 3001;

// 必须的中间件
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// 健康检查
Expand Down Expand Up @@ -177,6 +178,19 @@ async function startServer() {
startReminderScheduler(); // 启动图书到期提醒定时任务
startDueReminderScheduler();

// ========== 新增:预约过期清理定时任务(每5分钟执行一次) ==========
cron.schedule('*/5 * * * *', async () => {
try {
const result = await expireReservations();
if (result.processed > 0) {
console.log(`[${new Date().toISOString()}] 📅 预约过期清理: 处理 ${result.processed} 个,释放 ${result.released} 个副本`);
}
} catch (error) {
console.error(`[${new Date().toISOString()}] ❌ 预约过期清理失败:`, error.message);
}
});
console.log('⏰ 预约过期清理定时任务已启动(每5分钟执行一次)');

app.listen(port, () => {
console.log(`
╔═══════════════════════════════════════════════════════╗
Expand All @@ -190,6 +204,7 @@ async function startServer() {
║ 💾 Backup endpoints: /api/backups/* ║
║ ⏰ Auto backup every ${BACKUP_INTERVAL_MS / 3600000} hours ║
║ 📧 Reminder endpoints: /api/librarian/reminders/* ║
║ 📅 Reservation expiry cleanup: every 5 minutes ║
╚═══════════════════════════════════════════════════════╝
`);
});
Expand Down Expand Up @@ -224,4 +239,4 @@ process.on('SIGTERM', () => {
void shutdown('SIGTERM');
});

startServer();
startServer();
Loading