chore: prepare project for server deployment

This commit is contained in:
amb
2026-09-09 17:43:07 +08:00
commit d0bcff2311
83 changed files with 24163 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const { getDb } = require('../config/database');
const sessions = new Map();
const hashPassword = (password, salt = crypto.randomBytes(16).toString('hex')) => {
const hash = crypto.scryptSync(password, salt, 64).toString('hex');
return `${salt}:${hash}`;
};
const verifyPassword = (password, stored) => {
const [salt, expected] = String(stored || '').split(':');
if (!salt || !expected) return false;
const actual = crypto.scryptSync(password, salt, 64).toString('hex');
return crypto.timingSafeEqual(Buffer.from(actual, 'hex'), Buffer.from(expected, 'hex'));
};
class AdminAuthService {
static login(username, password) {
const user = getDb().prepare('SELECT * FROM admin_users WHERE username = ?').get(username);
if (!user || !verifyPassword(password, user.password_hash)) {
throw new Error('用户名或密码错误');
}
const token = crypto.randomBytes(32).toString('hex');
sessions.set(token, { userId: user.user_id, username: user.username, expiresAt: Date.now() + 8 * 60 * 60 * 1000 });
return { token, username: user.username, expiresIn: 8 * 60 * 60 };
}
static verify(token) {
const session = sessions.get(token);
if (!session || session.expiresAt < Date.now()) {
if (token) sessions.delete(token);
return null;
}
return session;
}
static changePassword(username, currentPassword, newPassword) {
if (!newPassword || newPassword.length < 8) throw new Error('新密码至少需要8位');
const db = getDb();
const user = db.prepare('SELECT * FROM admin_users WHERE username = ?').get(username);
if (!user || !verifyPassword(currentPassword, user.password_hash)) throw new Error('当前密码错误');
db.prepare('UPDATE admin_users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE username = ?')
.run(hashPassword(newPassword), username);
for (const [token, session] of sessions.entries()) {
if (session.username === username) sessions.delete(token);
}
return { success: true };
}
static logout(token) {
sessions.delete(token);
}
}
module.exports = AdminAuthService;
+220
View File
@@ -0,0 +1,220 @@
const Content = require('../models/Content');
const Page = require('../models/Page');
const Article = require('../models/Article');
const Case = require('../models/Case');
const FAQ = require('../models/FAQ');
const Capability = require('../models/Capability');
const Service = require('../models/Service');
const { getDb } = require('../config/database');
const Joi = require('joi');
class ContentService {
// 根据内容类型获取对应的模型
static getModelByType(contentType) {
const models = {
'page': Page,
'article': Article,
'case': Case,
'faq': FAQ,
'capability': Capability,
'service': Service
};
return models[contentType] || Content;
}
// 验证内容数据
static validateContent(data, contentType, isUpdate = false) {
const baseSchema = {
title: isUpdate ? Joi.string().min(1).max(255) : Joi.string().min(1).max(255).required(),
body: Joi.string().allow(null, ''),
publish_status: Joi.string().valid('draft', 'review', 'published', 'unpublished', 'historical'),
authority_level: Joi.number().integer().min(1).max(7),
version_no: Joi.number().integer().min(1),
version_note: Joi.string().allow(null, ''),
metadata: Joi.object()
};
// 根据内容类型添加特定字段验证
let specificSchema = {};
switch (contentType) {
case 'page':
specificSchema = {
slug: Joi.string().pattern(/^\/$|^[a-z0-9]+(?:-[a-z0-9]+)*$/),
summary: Joi.string().allow(null, ''),
template: Joi.string().allow(null, ''),
seo_title: Joi.string().allow(null, '').max(255),
seo_description: Joi.string().allow(null, '').max(500),
seo_keywords: Joi.string().allow(null, '')
};
break;
case 'article':
specificSchema = {
category: Joi.string().allow(null, ''),
tags: Joi.string().allow(null, ''),
cover_image: Joi.string().uri().allow(null, ''),
author: Joi.string().allow(null, '')
};
break;
case 'case':
specificSchema = {
client_name: Joi.string().allow(null, ''),
industry: Joi.string().allow(null, ''),
challenge: Joi.string().allow(null, ''),
solution: Joi.string().allow(null, ''),
result: Joi.string().allow(null, '')
};
break;
case 'faq':
specificSchema = {
question: Joi.string().allow(null, ''),
answer: Joi.string().allow(null, ''),
sort_order: Joi.number().integer().min(0)
};
break;
case 'capability':
specificSchema = {
capability_type: Joi.string().allow(null, ''),
features: Joi.string().allow(null, ''),
use_cases: Joi.string().allow(null, '')
};
break;
case 'service':
specificSchema = {
service_type: Joi.string().allow(null, ''),
pricing_model: Joi.string().allow(null, ''),
delivery_method: Joi.string().allow(null, '')
};
break;
}
const schema = Joi.object({ ...baseSchema, ...specificSchema });
return schema.validate(data, { abortEarly: false });
}
// 创建内容
static create(data, contentType) {
if (!contentType) {
throw new Error('内容类型不能为空');
}
const { error, value } = this.validateContent(data, contentType);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
const Model = this.getModelByType(contentType);
return Model.create({ ...value, content_type: contentType });
}
// 获取内容详情
static getById(contentId, contentType = null) {
let content;
if (contentType) {
const Model = this.getModelByType(contentType);
content = Model.findById(contentId);
} else {
content = Content.findById(contentId);
}
if (!content) {
throw new Error('内容不存在');
}
return content;
}
// 获取内容列表
static getList(contentType, options = {}) {
const Model = this.getModelByType(contentType);
return Model.findAll(options);
}
// 获取已发布的内容
static getPublished(contentType, options = {}) {
const Model = this.getModelByType(contentType);
return Model.findAll({
...options,
where: {
...options.where,
publish_status: 'published'
}
});
}
// 更新内容
static update(contentId, data, contentType = null) {
// 检查内容是否存在
const existingContent = Content.findById(contentId);
if (!existingContent) {
throw new Error('内容不存在');
}
// 如果指定了内容类型,检查是否匹配
if (contentType && existingContent.content_type !== contentType) {
throw new Error('内容类型不匹配');
}
const { error, value } = this.validateContent(data, existingContent.content_type, true);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
const Model = this.getModelByType(existingContent.content_type);
return Model.update(contentId, value);
}
// 删除内容
static delete(contentId) {
const content = Content.findById(contentId);
if (!content) {
throw new Error('内容不存在');
}
const Model = this.getModelByType(content.content_type);
return Model.delete(contentId);
}
// 内容状态流转
static transitionStatus(contentId, newStatus) {
return Content.transitionStatus(contentId, newStatus);
}
// 创建历史版本
static createHistoricalVersion(contentId) {
return Content.createHistoricalVersion(contentId);
}
// 获取内容总数
static count(contentType = null) {
if (contentType) {
const row = getDb().prepare('SELECT COUNT(*) AS count FROM contents WHERE content_type = ?').get(contentType);
return Number(row?.count || 0);
}
return Content.count();
}
// 分页查询
static paginate(contentType, page = 1, pageSize = 10, options = {}) {
const offset = (page - 1) * pageSize;
const Model = this.getModelByType(contentType);
const list = Model.findAll({
...options,
limit: pageSize,
offset
});
const total = this.count(contentType);
return {
list,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
}
module.exports = ContentService;
+146
View File
@@ -0,0 +1,146 @@
const DirectedRelationship = require('../models/DirectedRelationship');
const Entity = require('../models/Entity');
const Joi = require('joi');
class DirectedRelationshipService {
// 验证关系数据
static validateRelationship(data, isUpdate = false) {
const schema = Joi.object({
source_entity_id: isUpdate ? Joi.string().uuid() : Joi.string().uuid().required(),
relation_type: isUpdate ? Joi.string().min(1).max(100) : Joi.string().min(1).max(100).required(),
target_entity_id: isUpdate ? Joi.string().uuid() : Joi.string().uuid().required(),
rel_status: Joi.string().valid('active', 'inactive'),
authority_level: Joi.number().integer().min(1).max(7),
is_public: Joi.number().integer().valid(0, 1),
effective_at: Joi.date().iso(),
expire_at: Joi.date().iso().greater(Joi.ref('effective_at'))
});
return schema.validate(data, { abortEarly: false });
}
// 创建关系
static create(data) {
const { error, value } = this.validateRelationship(data);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
// 检查源实体是否存在
const sourceEntity = Entity.findById(value.source_entity_id);
if (!sourceEntity) {
throw new Error('源实体不存在');
}
// 检查目标实体是否存在
const targetEntity = Entity.findById(value.target_entity_id);
if (!targetEntity) {
throw new Error('目标实体不存在');
}
// 检查是否试图创建自引用关系
if (value.source_entity_id === value.target_entity_id) {
throw new Error('不能创建自引用关系');
}
return DirectedRelationship.create(value);
}
// 获取关系详情
static getById(relId) {
const relationship = DirectedRelationship.findById(relId);
if (!relationship) {
throw new Error('关系不存在');
}
return relationship;
}
// 获取关系列表
static getList(options = {}) {
return DirectedRelationship.findAll(options);
}
// 获取实体的出向关系
static getBySourceEntity(sourceEntityId, options = {}) {
return DirectedRelationship.findBySourceEntity(sourceEntityId, options);
}
// 获取实体的入向关系(反向查询)
static getByTargetEntity(targetEntityId, options = {}) {
return DirectedRelationship.findByTargetEntity(targetEntityId, options);
}
// 更新关系
static update(relId, data) {
// 检查关系是否存在
const existingRelationship = DirectedRelationship.findById(relId);
if (!existingRelationship) {
throw new Error('关系不存在');
}
// 如果要修改源实体或目标实体,检查新实体是否存在
if (data.source_entity_id && data.source_entity_id !== existingRelationship.source_entity_id) {
const sourceEntity = Entity.findById(data.source_entity_id);
if (!sourceEntity) {
throw new Error('源实体不存在');
}
}
if (data.target_entity_id && data.target_entity_id !== existingRelationship.target_entity_id) {
const targetEntity = Entity.findById(data.target_entity_id);
if (!targetEntity) {
throw new Error('目标实体不存在');
}
}
// 检查是否试图创建自引用关系
const sourceEntityId = data.source_entity_id || existingRelationship.source_entity_id;
const targetEntityId = data.target_entity_id || existingRelationship.target_entity_id;
if (sourceEntityId === targetEntityId) {
throw new Error('不能创建自引用关系');
}
const { error, value } = this.validateRelationship(data, true);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
return DirectedRelationship.update(relId, value);
}
// 删除关系
static delete(relId) {
const relationship = DirectedRelationship.findById(relId);
if (!relationship) {
throw new Error('关系不存在');
}
return DirectedRelationship.delete(relId);
}
// 获取关系总数
static count() {
return DirectedRelationship.count();
}
// 分页查询
static paginate(page = 1, pageSize = 10, options = {}) {
const offset = (page - 1) * pageSize;
const list = DirectedRelationship.findAll({
...options,
limit: pageSize,
offset
});
const total = this.count();
return {
list,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
}
module.exports = DirectedRelationshipService;
+142
View File
@@ -0,0 +1,142 @@
const EntityContentJoin = require('../models/EntityContentJoin');
const Entity = require('../models/Entity');
const Content = require('../models/Content');
const Joi = require('joi');
class EntityContentJoinService {
// 验证关联数据
static validateJoin(data, isUpdate = false) {
const schema = Joi.object({
entity_id: isUpdate ? Joi.string().uuid() : Joi.string().uuid().required(),
content_id: isUpdate ? Joi.string().uuid() : Joi.string().uuid().required(),
join_note: Joi.string().allow(null, '')
});
return schema.validate(data, { abortEarly: false });
}
// 创建关联
static create(data) {
const { error, value } = this.validateJoin(data);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
// 检查实体是否存在
const entity = Entity.findById(value.entity_id);
if (!entity) {
throw new Error('实体不存在');
}
// 检查内容是否存在
const content = Content.findById(value.content_id);
if (!content) {
throw new Error('内容不存在');
}
// 检查是否已存在相同的关联
const existingJoins = EntityContentJoin.findByEntityId(value.entity_id);
const duplicateJoin = existingJoins.find(join => join.content_id === value.content_id);
if (duplicateJoin) {
throw new Error('该实体与内容的关联已存在');
}
return EntityContentJoin.create(value);
}
// 获取关联详情
static getById(joinId) {
const join = EntityContentJoin.findById(joinId);
if (!join) {
throw new Error('关联不存在');
}
return join;
}
// 获取实体的所有关联
static getByEntityId(entityId) {
return EntityContentJoin.findByEntityId(entityId);
}
// 获取内容的所有关联
static getByContentId(contentId) {
return EntityContentJoin.findByContentId(contentId);
}
// 获取所有关联
static getList(options = {}) {
return EntityContentJoin.findAll(options);
}
// 更新关联
static update(joinId, data) {
// 检查关联是否存在
const existingJoin = EntityContentJoin.findById(joinId);
if (!existingJoin) {
throw new Error('关联不存在');
}
// 如果要修改实体或内容,检查新实体或内容是否存在
if (data.entity_id && data.entity_id !== existingJoin.entity_id) {
const entity = Entity.findById(data.entity_id);
if (!entity) {
throw new Error('实体不存在');
}
}
if (data.content_id && data.content_id !== existingJoin.content_id) {
const content = Content.findById(data.content_id);
if (!content) {
throw new Error('内容不存在');
}
}
const { error, value } = this.validateJoin(data, true);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
return EntityContentJoin.update(joinId, value);
}
// 删除关联
static delete(joinId) {
const join = EntityContentJoin.findById(joinId);
if (!join) {
throw new Error('关联不存在');
}
return EntityContentJoin.delete(joinId);
}
// 删除实体与内容的关联
static deleteByEntityAndContent(entityId, contentId) {
return EntityContentJoin.deleteByEntityAndContent(entityId, contentId);
}
// 获取关联总数
static count() {
return EntityContentJoin.count();
}
// 分页查询
static paginate(page = 1, pageSize = 10, options = {}) {
const offset = (page - 1) * pageSize;
const list = EntityContentJoin.findAll({
...options,
limit: pageSize,
offset
});
const total = this.count();
return {
list,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
}
module.exports = EntityContentJoinService;
+97
View File
@@ -0,0 +1,97 @@
const Entity = require('../models/Entity');
const Joi = require('joi');
class EntityService {
// 验证实体数据
static validateEntity(data, isUpdate = false) {
const schema = Joi.object({
name: isUpdate ? Joi.string().min(1).max(255) : Joi.string().min(1).max(255).required(),
definition: Joi.string().allow(null, ''),
description: Joi.string().allow(null, ''),
metadata: Joi.object()
});
return schema.validate(data, { abortEarly: false });
}
// 创建实体
static create(data) {
const { error, value } = this.validateEntity(data);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
return Entity.create(value);
}
// 获取实体详情
static getById(entityId) {
const entity = Entity.findById(entityId);
if (!entity) {
throw new Error('实体不存在');
}
return entity;
}
// 获取实体列表
static getList(options = {}) {
return Entity.findAll(options);
}
// 更新实体
static update(entityId, data) {
// 检查实体是否存在
const existingEntity = Entity.findById(entityId);
if (!existingEntity) {
throw new Error('实体不存在');
}
// 禁止修改entity_id
if (data.entity_id && data.entity_id !== entityId) {
throw new Error('不能修改entity_id');
}
const { error, value } = this.validateEntity(data, true);
if (error) {
throw new Error(`验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
return Entity.update(entityId, value);
}
// 删除实体
static delete(entityId) {
const entity = Entity.findById(entityId);
if (!entity) {
throw new Error('实体不存在');
}
return Entity.delete(entityId);
}
// 获取实体总数
static count() {
return Entity.count();
}
// 分页查询
static paginate(page = 1, pageSize = 10, options = {}) {
const offset = (page - 1) * pageSize;
const list = Entity.findAll({
...options,
limit: pageSize,
offset
});
const total = this.count();
return {
list,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
}
module.exports = EntityService;
+214
View File
@@ -0,0 +1,214 @@
const Entity = require('../models/Entity');
const DirectedRelationship = require('../models/DirectedRelationship');
const Content = require('../models/Content');
const EntityContentJoin = require('../models/EntityContentJoin');
class JsonLdService {
// 生成实体的JSON-LD结构化数据
static generateEntityJsonLd(entityId) {
const entity = Entity.findById(entityId);
if (!entity) {
throw new Error('实体不存在');
}
// 获取实体的关系
const sourceRelationships = DirectedRelationship.findBySourceEntity(entityId, {
where: { rel_status: 'active', is_public: 1 }
});
const targetRelationships = DirectedRelationship.findByTargetEntity(entityId, {
where: { rel_status: 'active', is_public: 1 }
});
// 获取实体的内容
const contentJoins = EntityContentJoin.findByEntityId(entityId);
const contentIds = contentJoins.map(join => join.content_id);
let contents = [];
if (contentIds.length > 0) {
const { getDb } = require('../config/database');
const db = getDb();
const placeholders = contentIds.map(() => '?').join(',');
const stmt = db.prepare(`SELECT * FROM contents WHERE content_id IN (${placeholders}) AND publish_status = 'published'`);
contents = stmt.all(...contentIds);
}
// 构建JSON-LD结构
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Thing',
'@id': `entity:${entity.entity_id}`,
'name': entity.name,
'description': entity.description || entity.definition || '',
'identifier': entity.entity_id
};
// 添加元数据
if (entity.metadata) {
const metadata = typeof entity.metadata === 'string' ? JSON.parse(entity.metadata) : entity.metadata;
Object.assign(jsonLd, metadata);
}
// 添加关系
if (sourceRelationships.length > 0) {
jsonLd['relatedTo'] = sourceRelationships.map(rel => ({
'@type': 'Relationship',
'relationshipType': rel.relation_type,
'target': {
'@type': 'Thing',
'@id': `entity:${rel.target_entity_id}`,
'identifier': rel.target_entity_id
}
}));
}
// 添加内容
if (contents.length > 0) {
jsonLd['associatedMedia'] = contents.map(content => ({
'@type': 'CreativeWork',
'@id': `content:${content.content_id}`,
'name': content.title,
'description': content.body ? content.body.substring(0, 200) : '',
'identifier': content.content_id
}));
}
return jsonLd;
}
// 生成内容的JSON-LD结构化数据
static generateContentJsonLd(contentId) {
const content = Content.findById(contentId);
if (!content) {
throw new Error('内容不存在');
}
// 获取内容的实体关联
const contentJoins = EntityContentJoin.findByContentId(contentId);
const entityIds = contentJoins.map(join => join.entity_id);
let entities = [];
if (entityIds.length > 0) {
const { getDb } = require('../config/database');
const db = getDb();
const placeholders = entityIds.map(() => '?').join(',');
const stmt = db.prepare(`SELECT * FROM entities WHERE entity_id IN (${placeholders})`);
entities = stmt.all(...entityIds);
}
// 根据内容类型构建不同的JSON-LD结构
let jsonLd = {
'@context': 'https://schema.org',
'@id': `content:${content.content_id}`,
'name': content.title,
'description': content.body ? content.body.substring(0, 200) : '',
'identifier': content.content_id
};
switch (content.content_type) {
case 'page':
jsonLd['@type'] = 'WebPage';
break;
case 'article':
jsonLd['@type'] = 'Article';
break;
case 'case':
jsonLd['@type'] = 'CaseStudy';
break;
case 'faq':
jsonLd['@type'] = 'FAQPage';
break;
case 'capability':
jsonLd['@type'] = 'Service';
break;
case 'service':
jsonLd['@type'] = 'Service';
break;
default:
jsonLd['@type'] = 'CreativeWork';
}
// 添加元数据
if (content.metadata) {
const metadata = typeof content.metadata === 'string' ? JSON.parse(content.metadata) : content.metadata;
Object.assign(jsonLd, metadata);
}
// 添加实体关联
if (entities.length > 0) {
jsonLd['about'] = entities.map(entity => ({
'@type': 'Thing',
'@id': `entity:${entity.entity_id}`,
'name': entity.name,
'identifier': entity.entity_id
}));
}
// 特定内容类型的字段
if (content.content_type === 'article') {
jsonLd['articleBody'] = content.body;
if (content.author) {
jsonLd['author'] = {
'@type': 'Person',
'name': content.author
};
}
}
if (content.content_type === 'faq') {
jsonLd['mainEntity'] = {
'@type': 'Question',
'name': content.question,
'acceptedAnswer': {
'@type': 'Answer',
'text': content.answer
}
};
}
return jsonLd;
}
// 生成所有已发布实体的JSON-LD列表
static generateAllEntitiesJsonLd() {
const entities = Entity.findAll();
return entities.map(entity => this.generateEntityJsonLd(entity.entity_id));
}
// 生成所有已发布内容的JSON-LD列表
static generateAllContentsJsonLd(contentType = null) {
let contents;
if (contentType) {
const { getDb } = require('../config/database');
const db = getDb();
const stmt = db.prepare('SELECT * FROM contents WHERE publish_status = ? AND content_type = ?');
contents = stmt.all('published', contentType);
} else {
const { getDb } = require('../config/database');
const db = getDb();
const stmt = db.prepare('SELECT * FROM contents WHERE publish_status = ?');
contents = stmt.all('published');
}
return contents.map(content => this.generateContentJsonLd(content.content_id));
}
// 生成站点地图的JSON-LD
static generateSitemapJsonLd() {
const entities = this.generateAllEntitiesJsonLd();
const contents = this.generateAllContentsJsonLd();
return {
'@context': 'https://schema.org',
'@type': 'WebSite',
'name': '阿美巴AI官网',
'description': '阿美巴AI官网 - 知识底座与CMS内核',
'hasPart': [
...entities,
...contents
]
};
}
}
module.exports = JsonLdService;
+210
View File
@@ -0,0 +1,210 @@
const Entity = require('../models/Entity');
const DirectedRelationship = require('../models/DirectedRelationship');
const Content = require('../models/Content');
const EntityContentJoin = require('../models/EntityContentJoin');
class PublicApiService {
// 获取已发布的实体
static getPublishedEntities(options = {}) {
return Entity.findAll({
...options,
where: {
...options.where
}
});
}
// 获取已发布的实体详情
static getPublishedEntityById(entityId) {
return Entity.findById(entityId);
}
// 获取已发布的关系
static getPublishedRelationships(options = {}) {
return DirectedRelationship.findAll({
...options,
where: {
...options.where,
rel_status: 'active',
is_public: 1
}
});
}
// 获取实体的已发布出向关系
static getPublishedRelationshipsBySource(entityId) {
return DirectedRelationship.findBySourceEntity(entityId, {
where: {
rel_status: 'active',
is_public: 1
}
});
}
// 获取实体的已发布入向关系
static getPublishedRelationshipsByTarget(entityId) {
return DirectedRelationship.findByTargetEntity(entityId, {
where: {
rel_status: 'active',
is_public: 1
}
});
}
// 获取已发布的内容
static getPublishedContents(contentType, options = {}) {
let query = 'SELECT * FROM contents WHERE publish_status = ?';
const params = ['published'];
if (contentType) {
query += ' AND content_type = ?';
params.push(contentType);
}
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` AND ${conditions.join(' AND ')}`;
params.push(...Object.values(options.where));
}
if (options.orderBy) {
query += ` ORDER BY ${options.orderBy}`;
}
if (options.limit) {
query += ` LIMIT ?`;
params.push(options.limit);
}
if (options.offset) {
query += ` OFFSET ?`;
params.push(options.offset);
}
const { getDb } = require('../config/database');
const db = getDb();
const stmt = db.prepare(query);
const rows = stmt.all(...params);
return rows.map(row => {
if (row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
});
}
// 获取已发布的内容详情
static getPublishedContentById(contentId) {
const content = Content.findById(contentId);
if (content && content.publish_status === 'published') {
return content;
}
return null;
}
// 获取实体的内容关联(已发布的内容)
static getEntityPublishedContents(entityId) {
const joins = EntityContentJoin.findByEntityId(entityId);
const contentIds = joins.map(join => join.content_id);
if (contentIds.length === 0) {
return [];
}
const { getDb } = require('../config/database');
const db = getDb();
const placeholders = contentIds.map(() => '?').join(',');
const stmt = db.prepare(`SELECT * FROM contents WHERE content_id IN (${placeholders}) AND publish_status = 'published'`);
return stmt.all(...contentIds);
}
// 获取内容的实体关联
static getContentEntities(contentId) {
const joins = EntityContentJoin.findByContentId(contentId);
const entityIds = joins.map(join => join.entity_id);
if (entityIds.length === 0) {
return [];
}
const { getDb } = require('../config/database');
const db = getDb();
const placeholders = entityIds.map(() => '?').join(',');
const stmt = db.prepare(`SELECT * FROM entities WHERE entity_id IN (${placeholders})`);
return stmt.all(...entityIds);
}
// 搜索实体
static searchEntities(keyword, options = {}) {
const { getDb } = require('../config/database');
const db = getDb();
let query = 'SELECT * FROM entities WHERE (name LIKE ? OR definition LIKE ? OR description LIKE ?)';
const params = [`%${keyword}%`, `%${keyword}%`, `%${keyword}%`];
if (options.orderBy) {
query += ` ORDER BY ${options.orderBy}`;
}
if (options.limit) {
query += ` LIMIT ?`;
params.push(options.limit);
}
if (options.offset) {
query += ` OFFSET ?`;
params.push(options.offset);
}
const stmt = db.prepare(query);
const rows = stmt.all(...params);
return rows.map(row => {
if (row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
});
}
// 搜索内容
static searchContents(keyword, contentType = null, options = {}) {
const { getDb } = require('../config/database');
const db = getDb();
let query = 'SELECT * FROM contents WHERE publish_status = ? AND (title LIKE ? OR body LIKE ?)';
const params = ['published', `%${keyword}%`, `%${keyword}%`];
if (contentType) {
query += ' AND content_type = ?';
params.push(contentType);
}
if (options.orderBy) {
query += ` ORDER BY ${options.orderBy}`;
}
if (options.limit) {
query += ` LIMIT ?`;
params.push(options.limit);
}
if (options.offset) {
query += ` OFFSET ?`;
params.push(options.offset);
}
const stmt = db.prepare(query);
const rows = stmt.all(...params);
return rows.map(row => {
if (row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
});
}
}
module.exports = PublicApiService;