223 lines
6.4 KiB
JavaScript
Raw Permalink Normal View History

/**
*
* 团队书库引擎 · Library Engine Service
*
*
* 智库节点 Phase 3 · ZY-SVR-006
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 功能:
* - 团队共享书库男频/女频分区
* - 去重存储同一本书只存一份成员书架用引用
* - 书籍分类管理标签/分区/推荐
* - 书籍评分与评论
*
*
*/
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const DATA_DIR = path.join(__dirname, '..', 'data');
const LIBRARY_FILE = path.join(DATA_DIR, 'team-library.json');
const CATEGORIES = ['男频', '女频', '无CP', '百合', '耽美', '其他'];
// 内存数据 (null-prototype 防原型链污染)
let libraryData = Object.create(null);
function sanitizeId(id) {
if (!id || typeof id !== 'string') return null;
if (id === '__proto__' || id === 'constructor' || id === 'prototype') return null;
if (id.includes('..') || id.includes('/') || id.includes('\\')) return null;
return id;
}
function init() {
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
load();
}
function load() {
try {
if (fs.existsSync(LIBRARY_FILE)) {
const raw = JSON.parse(fs.readFileSync(LIBRARY_FILE, 'utf8'));
libraryData = Object.create(null);
if (raw.books) libraryData.books = raw.books;
else libraryData.books = [];
} else {
libraryData.books = [];
}
} catch {
libraryData.books = [];
}
}
function save() {
try {
fs.writeFileSync(LIBRARY_FILE, JSON.stringify(libraryData, null, 2), 'utf8');
} catch (err) {
console.error('[LibraryEngine] 保存失败:', err.message);
}
}
/**
* 添加书籍到团队书库去重同书名+同作者视为重复
*/
function addBook({ book_name, author, category, tags, source_file, added_by }) {
if (!book_name) throw new Error('book_name 为必填项');
// 去重检查
const existing = libraryData.books.find(
b => b.book_name === book_name.trim() && b.author === (author || '未知').trim()
);
if (existing) {
return { added: false, message: '书库中已存在同名同作者书籍', book: existing };
}
const bookId = `LIB-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
const book = {
book_id: bookId,
book_name: book_name.trim(),
author: (author || '未知').trim(),
category: CATEGORIES.includes(category) ? category : '其他',
tags: Array.isArray(tags) ? tags.slice(0, 10) : [],
source_file: source_file || null,
added_by: added_by || 'system',
rating: 0,
rating_count: 0,
ref_count: 0,
reviews: [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
libraryData.books.push(book);
save();
return { added: true, book };
}
/**
* 列表支持分区过滤搜索排序
*/
function listBooks({ category, search, sort, limit } = {}) {
let result = [...libraryData.books];
if (category && CATEGORIES.includes(category)) {
result = result.filter(b => b.category === category);
}
if (search) {
const q = search.toLowerCase();
result = result.filter(b =>
b.book_name.toLowerCase().includes(q) ||
b.author.toLowerCase().includes(q) ||
(b.tags || []).some(t => t.toLowerCase().includes(q))
);
}
if (sort === 'rating') {
result.sort((a, b) => b.rating - a.rating);
} else if (sort === 'popular') {
result.sort((a, b) => b.ref_count - a.ref_count);
} else {
result.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
}
if (limit) result = result.slice(0, limit);
return result;
}
/**
* 获取单本书详情
*/
function getBook(bookId) {
if (!sanitizeId(bookId)) return null;
return libraryData.books.find(b => b.book_id === bookId) || null;
}
/**
* 添加评分
*/
function rateBook(bookId, score, memberId) {
if (!sanitizeId(bookId)) throw new Error('非法 bookId');
const book = libraryData.books.find(b => b.book_id === bookId);
if (!book) throw new Error('书籍不存在');
if (score < 1 || score > 5) throw new Error('评分范围 1-5');
// 简化:每人只算最新评分
const total = book.rating * book.rating_count + score;
book.rating_count++;
book.rating = Math.round((total / book.rating_count) * 10) / 10;
book.updated_at = new Date().toISOString();
save();
return { rating: book.rating, rating_count: book.rating_count };
}
/**
* 添加评论
*/
function addReview(bookId, { member_id, content }) {
if (!sanitizeId(bookId)) throw new Error('非法 bookId');
const book = libraryData.books.find(b => b.book_id === bookId);
if (!book) throw new Error('书籍不存在');
if (!content || content.trim().length === 0) throw new Error('评论内容不能为空');
const review = {
member_id: member_id || 'anonymous',
content: content.trim().slice(0, 500),
created_at: new Date().toISOString()
};
if (!book.reviews) book.reviews = [];
book.reviews.push(review);
book.updated_at = new Date().toISOString();
save();
return review;
}
/**
* 引用计数成员添加到个人书架时+1
*/
function incrementRef(bookId) {
const book = libraryData.books.find(b => b.book_id === bookId);
if (book) {
book.ref_count = (book.ref_count || 0) + 1;
save();
}
}
/**
* 统计
*/
function getStats() {
const books = libraryData.books;
const catCounts = {};
for (const cat of CATEGORIES) {
catCounts[cat] = books.filter(b => b.category === cat).length;
}
return {
total_books: books.length,
categories: catCounts,
total_reviews: books.reduce((s, b) => s + (b.reviews ? b.reviews.length : 0), 0),
avg_rating: books.length > 0
? Math.round(books.reduce((s, b) => s + b.rating, 0) / books.length * 10) / 10
: 0
};
}
init();
module.exports = {
addBook, listBooks, getBook, rateBook, addReview,
incrementRef, getStats, CATEGORIES
};