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
+86
View File
@@ -0,0 +1,86 @@
require('dotenv').config();
const SyncMySQL = require('sync-mysql');
let db;
const requiredConfig = ['DB_HOST', 'DB_PORT', 'DB_NAME', 'DB_USER', 'DB_PASSWORD'];
class MySqlStatement {
constructor(connection, sql) {
this.connection = connection;
this.sql = sql;
}
all(...params) {
const result = this.connection.query(this.sql, params);
return Array.isArray(result) ? result : [];
}
get(...params) {
const rows = this.all(...params);
return rows[0];
}
run(...params) {
const result = this.connection.query(this.sql, params) || {};
return {
changes: result.affectedRows || 0,
lastInsertRowid: result.insertId || 0,
};
}
}
class MySqlDatabase {
constructor(config) {
this.connection = new SyncMySQL(config);
}
prepare(sql) {
return new MySqlStatement(this.connection, sql);
}
exec(sql) {
// 当前迁移脚本只包含独立的CREATE TABLE语句,可安全按分号拆分执行。
sql.split(';')
.map(statement => statement.trim())
.filter(Boolean)
.forEach(statement => this.connection.query(statement));
}
close() {
try {
this.connection.dispose();
} catch (error) {
// sync-mysql的RPC worker已提前退出时,关闭过程不应阻断服务退出。
console.warn('MySQL连接关闭提示:', error.message);
}
}
}
const getDb = () => {
if (!db) {
const missing = requiredConfig.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`MySQL配置缺失: ${missing.join(', ')}`);
}
db = new MySqlDatabase({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
charset: 'utf8mb4',
});
}
return db;
};
const closeDb = () => {
if (db) {
db.close();
db = null;
}
};
module.exports = { getDb, closeDb };
+268
View File
@@ -0,0 +1,268 @@
const { getDb, closeDb } = require('../config/database');
const createTables = () => {
const db = getDb();
// Entity表 - 知识实体表(P1-01增加entity_type,P1-02增加status/public_flag)
db.exec(`
CREATE TABLE IF NOT EXISTS entities (
entity_id VARCHAR(36) PRIMARY KEY,
entity_type VARCHAR(64) DEFAULT 'concept',
name TEXT NOT NULL,
definition TEXT,
description TEXT,
status VARCHAR(32) DEFAULT 'active',
public_flag BOOLEAN DEFAULT 1,
authority_level INTEGER DEFAULT 1,
metadata TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// DirectedRelationship表 - 有向实体关系表
db.exec(`
CREATE TABLE IF NOT EXISTS directed_relationships (
rel_id VARCHAR(36) PRIMARY KEY,
source_entity_id VARCHAR(36) NOT NULL,
relation_type VARCHAR(255) NOT NULL,
target_entity_id VARCHAR(36) NOT NULL,
rel_status VARCHAR(32) DEFAULT 'active',
authority_level INTEGER DEFAULT 1,
is_public BOOLEAN DEFAULT 0,
effective_at DATETIME,
expire_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (source_entity_id) REFERENCES entities(entity_id),
FOREIGN KEY (target_entity_id) REFERENCES entities(entity_id)
)
`);
// Content基础表 - 内容基础模型
db.exec(`
CREATE TABLE IF NOT EXISTS contents (
content_id VARCHAR(36) PRIMARY KEY,
title TEXT NOT NULL,
body TEXT,
content_type VARCHAR(32) NOT NULL,
publish_status VARCHAR(32) DEFAULT 'draft',
authority_level INTEGER DEFAULT 1,
version_no INTEGER DEFAULT 1,
version_note TEXT,
metadata TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
archived_at DATETIME
)
`);
// Page表 - 页面扩展(P1-03增加summary)
db.exec(`
CREATE TABLE IF NOT EXISTS pages (
page_id VARCHAR(36) PRIMARY KEY,
slug VARCHAR(255) UNIQUE,
template VARCHAR(255),
summary TEXT,
seo_title TEXT,
seo_description TEXT,
seo_keywords TEXT,
FOREIGN KEY (page_id) REFERENCES contents(content_id)
)
`);
// Article表 - 文章扩展(P1-04增加published_at)
db.exec(`
CREATE TABLE IF NOT EXISTS articles (
article_id VARCHAR(36) PRIMARY KEY,
category VARCHAR(255),
tags TEXT,
cover_image VARCHAR(1024),
author VARCHAR(255),
published_at DATETIME,
FOREIGN KEY (article_id) REFERENCES contents(content_id)
)
`);
// Case表 - 案例扩展(P0-01扩展完整案例字段)
db.exec(`
CREATE TABLE IF NOT EXISTS cases (
case_id VARCHAR(36) PRIMARY KEY,
client_name TEXT,
industry TEXT,
challenge TEXT,
solution TEXT,
result TEXT,
background TEXT,
enterprise_stage TEXT,
original_goal TEXT,
current_state TEXT,
surface_problem TEXT,
amb_judgment TEXT,
key_obstacle TEXT,
root_cause TEXT,
evolution_path TEXT,
implementation TEXT,
case_significance TEXT,
FOREIGN KEY (case_id) REFERENCES contents(content_id)
)
`);
// FAQ表 - 常见问题扩展
db.exec(`
CREATE TABLE IF NOT EXISTS faqs (
faq_id VARCHAR(36) PRIMARY KEY,
question TEXT,
answer TEXT,
sort_order INTEGER DEFAULT 0,
FOREIGN KEY (faq_id) REFERENCES contents(content_id)
)
`);
// Capability表 - 能力扩展
db.exec(`
CREATE TABLE IF NOT EXISTS capabilities (
capability_id VARCHAR(36) PRIMARY KEY,
capability_type TEXT,
features TEXT,
use_cases TEXT,
FOREIGN KEY (capability_id) REFERENCES contents(content_id)
)
`);
// Service表 - 服务扩展
db.exec(`
CREATE TABLE IF NOT EXISTS services (
service_id VARCHAR(36) PRIMARY KEY,
service_type TEXT,
pricing_model TEXT,
delivery_method TEXT,
FOREIGN KEY (service_id) REFERENCES contents(content_id)
)
`);
// EntityContentJoin表 - 实体-内容多对多关联中间表
db.exec(`
CREATE TABLE IF NOT EXISTS entity_content_joins (
join_id VARCHAR(36) PRIMARY KEY,
entity_id VARCHAR(36) NOT NULL,
content_id VARCHAR(36) NOT NULL,
join_note TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (entity_id) REFERENCES entities(entity_id),
FOREIGN KEY (content_id) REFERENCES contents(content_id)
)
`);
// 管理员账号表
db.exec(`
CREATE TABLE IF NOT EXISTS admin_users (
user_id VARCHAR(36) PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// 前台可编辑站点配置
db.exec(`
CREATE TABLE IF NOT EXISTS site_settings (
setting_key VARCHAR(128) PRIMARY KEY,
setting_value TEXT,
setting_group VARCHAR(64) DEFAULT 'general',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('数据库表创建完成');
};
const migrate = () => {
try {
createTables();
seedAdminAndSettings();
console.log('数据库迁移完成');
} catch (error) {
console.error('数据库迁移失败:', error);
throw error;
}
};
const seedAdminAndSettings = () => {
const db = getDb();
const crypto = require('crypto');
const user = db.prepare('SELECT user_id FROM admin_users WHERE username = ?').get('admin');
if (!user) {
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.scryptSync('已移除的历史密码', salt, 64).toString('hex');
db.prepare(`
INSERT INTO admin_users (user_id, username, password_hash)
VALUES (?, ?, ?)
`).run(require('uuid').v4(), 'admin', `${salt}:${hash}`);
console.log('默认管理员账号已创建: admin');
}
const defaults = {
'site.name': ['阿美巴AI', 'general'],
'site.footerCopyright': ['© 2026 阿美巴AI. All rights reserved.', 'general'],
'site.footerEmail': ['ameiba0258@gmail.com', 'general'],
'site.footerPhone': ['4009080258', 'general'],
'site.footerAddress': ['四川省成都市双流区正兴街道湖畔路北段366号1栋3楼1号', 'general'],
'site.icp': ['蜀ICP备2025143754号-7', 'general'],
'home.heroTitle': ['阿美巴(Ameiba)', 'home'],
'home.heroSubtitle': ['AI时代企业进化平台', 'home'],
'home.heroDescription': ['我们帮助企业在AI时代实现系统化进化,从战略顶层设计到组织重塑,从经营增长到AI部署,提供完整的企业进化能力与服务。', 'home'],
'contact.title': ['联系我们', 'contact'],
'contact.contactTitle': ['联系方式', 'contact'],
'contact.email': ['contact@ameiba.com', 'contact'],
'contact.phone': ['待填充', 'contact'],
'contact.address': ['待填充', 'contact'],
'contact.serviceHoursTitle': ['服务时间', 'contact'],
'contact.serviceHours': ['周一至周五:9:00 - 18:00', 'contact'],
'contact.consultationTitle': ['咨询服务', 'contact'],
'contact.consultationIntro': ['我们提供以下咨询服务:', 'contact'],
'contact.consultationItems': ['企业进化诊断\n进化路径规划\n战略咨询\n组织变革咨询\nAI部署咨询', 'contact'],
'contact.cooperationTitle': ['合作方式', 'contact'],
'contact.cooperationItems': ['顾问服务\n咨询服务\n企业陪跑\n定制化服务', 'contact'],
'contact.faqTitle': ['常见问题', 'contact'],
'contact.faqText': ['如有其他问题,请查看', 'contact'],
'contact.relatedTitle': ['相关知识', 'contact'],
};
const pageDefaults = {
about: ['阿美巴是谁', '阿美巴(Ameiba)是AI时代企业进化平台,专注于帮助企业实现系统化进化。'],
evolution: ['企业进化', '企业进化是企业围绕长期发展目标持续调整、重构与升级的过程。'],
value: ['四大价值', '价值创造、价值传递、价值交付、价值支持共同构成企业经营的价值体系。'],
amb: ['A/M-B三态模型:AI时代企业进化状态识别框架', 'A/M-B模型用于帮助企业识别自身所处的进化状态,并明确下一步行动。'],
capabilities: ['企业进化能力地图', '从战略、经营、组织到AI部署,构建企业持续进化所需要的能力地图。'],
cases: ['企业进化案例', '通过真实案例,展示企业在经营环境中的目标、障碍、进化路径与实施过程。'],
knowledge: ['知识中心', '集中展示企业进化相关的知识、方法和实践内容。'],
faq: ['常见问题', '以下是关于企业进化的常见问题解答。'],
};
Object.entries(pageDefaults).forEach(([page, [title, intro]]) => {
defaults[`${page}.title`] = [title, page];
defaults[`${page}.intro`] = [intro, page];
});
const extraDefaults = {
'amb.whatTitle': ['什么是A/M-B', 'amb'], 'amb.whatNote': ['A/M-B不是产品套餐,不是收费等级,不是强制线性路径,不是所有企业必须经历的三个固定阶段。', 'amb'], 'amb.whatPurpose': ['A/M-B的作用,是帮助企业识别自身所处状态,并进一步判断当前需要完成的关键进化动作。', 'amb'], 'amb.statesTitle': ['三种状态', 'amb'], 'amb.stateA': ['A态·入态|企业开始进入AI时代企业进化的一种状态', 'amb'], 'amb.stateM': ['M态·优态|企业进一步围绕现有经营与管理体系进行优化的一种状态', 'amb'], 'amb.stateB': ['B态·越态|企业突破原有运行边界、形成新的企业运行能力的一种状态', 'amb'], 'amb.recognitionTitle': ['状态识别', 'amb'], 'amb.recognitionText': ['企业可以根据自身情况处于不同状态,也不意味着所有企业必须从A到M再到B。\n企业需要根据自身发展阶段和实际情况,判断当前所处的状态,并确定需要完成的关键进化动作。', 'amb'],
'evolution.definitionTitle': ['什么是企业进化', 'evolution'], 'evolution.definitionMore': ['增长更多体现结果层变化。进化更强调底层能力、系统与企业状态的持续调整和升级。AI是时代变量和重要赋能工具,不是企业进化本身的终点。', 'evolution'], 'evolution.whyTitle': ['为什么需要企业进化', 'evolution'], 'evolution.whyIntro': ['在AI时代,企业面临着前所未有的挑战:', 'evolution'], 'evolution.whyItems': ['技术快速迭代,传统业务模式面临冲击\n客户需求变化,个性化、智能化要求提高\n竞争环境加剧,需要更高效的运营模式\n人才结构变化,需要新的组织能力', 'evolution'],
'value.overviewTitle': ['价值体系概述', 'value'], 'value.valuesTitle': ['四大价值', 'value'], 'value.relationTitle': ['价值体系的关系', 'value'], 'value.relationText': ['四大价值相互关联、相互支撑。价值创造是基础,价值传递是桥梁,价值交付是结果,价值支持是保障。企业需要在这四个维度上协调发展,才能实现持续增长。', 'value'],
'about.positionTitle': ['定位', 'about'], 'about.positionText': ['阿美巴(Ameiba)是AI时代企业进化平台,专注于帮助企业实现系统化进化,从战略顶层设计到组织重塑,从经营增长到AI部署,提供完整的企业进化能力与服务。', 'about'], 'about.existTitle': ['为什么存在', 'about'], 'about.existText': ['在AI时代,企业面临着前所未有的挑战与机遇。传统的企业管理方法已无法适应快速变化的环境,企业需要一套系统化的进化方法,实现从传统模式向AI时代模式的转型。\n阿美巴的存在,就是为了帮助企业完成这一进化过程,实现可持续增长与发展。', 'about'], 'about.audienceTitle': ['服务对象', 'about'], 'about.audienceText': ['我们服务的对象是那些希望在AI时代实现转型与增长的企业,无论企业当前处于哪个发展阶段,我们都能提供适合的进化路径与能力支持。', 'about'], 'about.historyTitle': ['发展历程', 'about'], 'about.historyText': ['待填充', 'about'],
'capabilities.overviewTitle': ['能力概述', 'capabilities'], 'capabilities.categoryTitle': ['能力分类', 'capabilities'], 'cases.overviewTitle': ['案例概述', 'cases'], 'cases.listTitle': ['案例列表', 'cases'], 'knowledge.systemTitle': ['知识体系', 'knowledge'], 'knowledge.categoryTitle': ['知识分类', 'knowledge'], 'knowledge.articleTitle': ['最新文章', 'knowledge'], 'faq.overviewTitle': ['FAQ概述', 'faq'], 'faq.listTitle': ['问题列表', 'faq'],
};
Object.entries(extraDefaults).forEach(([key, [value, group]]) => {
defaults[key] = [value, group];
});
Object.entries(defaults).forEach(([key, [value, group]]) => {
const existing = db.prepare('SELECT setting_key FROM site_settings WHERE setting_key = ?').get(key);
if (!existing) {
db.prepare('INSERT INTO site_settings (setting_key, setting_value, setting_group) VALUES (?, ?, ?)').run(key, value, group);
}
});
};
if (require.main === module) {
migrate();
}
module.exports = { migrate, createTables, seedAdminAndSettings };
+105
View File
@@ -0,0 +1,105 @@
const express = require('express');
const cors = require('cors');
const path = require('path');
require('dotenv').config();
const { getDb, closeDb } = require('./config/database');
const { migrate } = require('./database/migrate');
// 路由导入
const entityRoutes = require('./routes/entityRoutes');
const relationshipRoutes = require('./routes/relationshipRoutes');
const contentRoutes = require('./routes/contentRoutes');
const entityContentJoinRoutes = require('./routes/entityContentJoinRoutes');
const publicApiRoutes = require('./routes/publicApiRoutes');
const jsonLdRoutes = require('./routes/jsonLdRoutes');
const adminRoutes = require('./routes/adminRoutes');
const app = express();
const PORT = process.env.PORT || 24731;
// 中间件
app.use(cors({ origin: true, credentials: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 静态文件服务
app.use('/static', express.static(path.join(__dirname, '../public')));
// 路由
app.use('/api/entities', entityRoutes);
app.use('/api/relationships', relationshipRoutes);
app.use('/api/contents', contentRoutes);
app.use('/api/joins', entityContentJoinRoutes);
app.use('/api/public', publicApiRoutes);
app.use('/api/jsonld', jsonLdRoutes);
app.use('/api/admin', adminRoutes);
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// 首页
app.get('/', (req, res) => {
res.json({
name: '阿美巴AI官网 MVP-BASE',
version: '1.0.0',
description: '第一期底座开发 - 知识模型、关系模型、内容模型、CMS治理内核',
endpoints: {
entities: '/api/entities',
relationships: '/api/relationships',
contents: '/api/contents',
joins: '/api/joins',
publicApi: '/api/public',
jsonLd: '/api/jsonld'
}
});
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error('服务器错误:', err);
res.status(500).json({ error: '服务器内部错误' });
});
// 启动服务器
const startServer = async () => {
try {
// 初始化数据库
console.log('正在初始化数据库...');
migrate();
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
console.log('API文档:');
console.log(` 实体管理: http://localhost:${PORT}/api/entities`);
console.log(` 关系管理: http://localhost:${PORT}/api/relationships`);
console.log(` 内容管理: http://localhost:${PORT}/api/contents`);
console.log(` 关联管理: http://localhost:${PORT}/api/joins`);
console.log(` 公开API: http://localhost:${PORT}/api/public`);
console.log(` JSON-LD: http://localhost:${PORT}/api/jsonld`);
});
} catch (error) {
console.error('启动服务器失败:', error);
process.exit(1);
}
};
// 优雅关闭
process.on('SIGINT', () => {
console.log('正在关闭服务器...');
closeDb();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('正在关闭服务器...');
closeDb();
process.exit(0);
});
startServer();
module.exports = app;
+153
View File
@@ -0,0 +1,153 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class Article extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'article';
this.article_id = data.article_id || this.content_id;
this.category = data.category || null;
this.tags = data.tags || null;
this.cover_image = data.cover_image || null;
this.author = data.author || null;
this.published_at = data.published_at || null;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'article'
};
const content = super.create(contentData);
// 再创建Article扩展记录
const stmt = db.prepare(`
INSERT INTO articles (article_id, category, tags, cover_image, author, published_at)
VALUES (?, ?, ?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.category || null,
data.tags || null,
data.cover_image || null,
data.author || null,
data.published_at || null
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, a.article_id, a.category, a.tags, a.cover_image, a.author, a.published_at
FROM contents c
LEFT JOIN articles a ON c.content_id = a.article_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.article_id;
delete contentData.category;
delete contentData.tags;
delete contentData.cover_image;
delete contentData.author;
delete contentData.published_at;
super.update(contentId, contentData);
// 更新Article扩展部分
const articleData = {};
if (data.category !== undefined) articleData.category = data.category;
if (data.tags !== undefined) articleData.tags = data.tags;
if (data.cover_image !== undefined) articleData.cover_image = data.cover_image;
if (data.author !== undefined) articleData.author = data.author;
if (data.published_at !== undefined) articleData.published_at = data.published_at;
if (Object.keys(articleData).length > 0) {
const setClause = Object.keys(articleData).map(key => `${key} = ?`).join(', ');
const values = Object.values(articleData);
const stmt = db.prepare(`UPDATE articles SET ${setClause} WHERE article_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除Article扩展记录
const stmt1 = db.prepare('DELETE FROM articles WHERE article_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, a.article_id, a.category, a.tags, a.cover_image, a.author, a.published_at
FROM contents c
LEFT JOIN articles a ON c.content_id = a.article_id
WHERE c.content_type = 'article'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'category' || key === 'tags' || key === 'cover_image' || key === 'author' || key === 'published_at') {
return `a.${key} = ?`;
}
return `c.${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 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 = Article;
+145
View File
@@ -0,0 +1,145 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class Capability extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'capability';
this.capability_id = data.capability_id || this.content_id;
this.capability_type = data.capability_type || null;
this.features = data.features || null;
this.use_cases = data.use_cases || null;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'capability'
};
const content = super.create(contentData);
// 再创建Capability扩展记录
const stmt = db.prepare(`
INSERT INTO capabilities (capability_id, capability_type, features, use_cases)
VALUES (?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.capability_type || null,
data.features || null,
data.use_cases || null
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, cap.capability_id, cap.capability_type, cap.features, cap.use_cases
FROM contents c
LEFT JOIN capabilities cap ON c.content_id = cap.capability_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.capability_id;
delete contentData.capability_type;
delete contentData.features;
delete contentData.use_cases;
super.update(contentId, contentData);
// 更新Capability扩展部分
const capabilityData = {};
if (data.capability_type !== undefined) capabilityData.capability_type = data.capability_type;
if (data.features !== undefined) capabilityData.features = data.features;
if (data.use_cases !== undefined) capabilityData.use_cases = data.use_cases;
if (Object.keys(capabilityData).length > 0) {
const setClause = Object.keys(capabilityData).map(key => `${key} = ?`).join(', ');
const values = Object.values(capabilityData);
const stmt = db.prepare(`UPDATE capabilities SET ${setClause} WHERE capability_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除Capability扩展记录
const stmt1 = db.prepare('DELETE FROM capabilities WHERE capability_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, cap.capability_id, cap.capability_type, cap.features, cap.use_cases
FROM contents c
LEFT JOIN capabilities cap ON c.content_id = cap.capability_id
WHERE c.content_type = 'capability'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'capability_type' || key === 'features' || key === 'use_cases') {
return `cap.${key} = ?`;
}
return `c.${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 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 = Capability;
+207
View File
@@ -0,0 +1,207 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class Case extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'case';
this.case_id = data.case_id || this.content_id;
this.client_name = data.client_name || null;
this.industry = data.industry || null;
this.challenge = data.challenge || null;
this.solution = data.solution || null;
this.result = data.result || null;
// P0-01扩展字段
this.background = data.background || null;
this.enterprise_stage = data.enterprise_stage || null;
this.original_goal = data.original_goal || null;
this.current_state = data.current_state || null;
this.surface_problem = data.surface_problem || null;
this.amb_judgment = data.amb_judgment || null;
this.key_obstacle = data.key_obstacle || null;
this.root_cause = data.root_cause || null;
this.evolution_path = data.evolution_path || null;
this.implementation = data.implementation || null;
this.case_significance = data.case_significance || null;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'case'
};
const content = super.create(contentData);
// 再创建Case扩展记录(P0-01扩展完整案例字段)
const stmt = db.prepare(`
INSERT INTO cases (case_id, client_name, industry, challenge, solution, result,
background, enterprise_stage, original_goal, current_state, surface_problem,
amb_judgment, key_obstacle, root_cause, evolution_path, implementation, case_significance)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.client_name || null,
data.industry || null,
data.challenge || null,
data.solution || null,
data.result || null,
data.background || null,
data.enterprise_stage || null,
data.original_goal || null,
data.current_state || null,
data.surface_problem || null,
data.amb_judgment || null,
data.key_obstacle || null,
data.root_cause || null,
data.evolution_path || null,
data.implementation || null,
data.case_significance || null
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, cs.case_id, cs.client_name, cs.industry, cs.challenge, cs.solution, cs.result,
cs.background, cs.enterprise_stage, cs.original_goal, cs.current_state, cs.surface_problem,
cs.amb_judgment, cs.key_obstacle, cs.root_cause, cs.evolution_path, cs.implementation, cs.case_significance
FROM contents c
LEFT JOIN cases cs ON c.content_id = cs.case_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.case_id;
delete contentData.client_name;
delete contentData.industry;
delete contentData.challenge;
delete contentData.solution;
delete contentData.result;
delete contentData.background;
delete contentData.enterprise_stage;
delete contentData.original_goal;
delete contentData.current_state;
delete contentData.surface_problem;
delete contentData.amb_judgment;
delete contentData.key_obstacle;
delete contentData.root_cause;
delete contentData.evolution_path;
delete contentData.implementation;
delete contentData.case_significance;
super.update(contentId, contentData);
// 更新Case扩展部分(P0-01扩展完整案例字段)
const caseData = {};
if (data.client_name !== undefined) caseData.client_name = data.client_name;
if (data.industry !== undefined) caseData.industry = data.industry;
if (data.challenge !== undefined) caseData.challenge = data.challenge;
if (data.solution !== undefined) caseData.solution = data.solution;
if (data.result !== undefined) caseData.result = data.result;
if (data.background !== undefined) caseData.background = data.background;
if (data.enterprise_stage !== undefined) caseData.enterprise_stage = data.enterprise_stage;
if (data.original_goal !== undefined) caseData.original_goal = data.original_goal;
if (data.current_state !== undefined) caseData.current_state = data.current_state;
if (data.surface_problem !== undefined) caseData.surface_problem = data.surface_problem;
if (data.amb_judgment !== undefined) caseData.amb_judgment = data.amb_judgment;
if (data.key_obstacle !== undefined) caseData.key_obstacle = data.key_obstacle;
if (data.root_cause !== undefined) caseData.root_cause = data.root_cause;
if (data.evolution_path !== undefined) caseData.evolution_path = data.evolution_path;
if (data.implementation !== undefined) caseData.implementation = data.implementation;
if (data.case_significance !== undefined) caseData.case_significance = data.case_significance;
if (Object.keys(caseData).length > 0) {
const setClause = Object.keys(caseData).map(key => `${key} = ?`).join(', ');
const values = Object.values(caseData);
const stmt = db.prepare(`UPDATE cases SET ${setClause} WHERE case_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除Case扩展记录
const stmt1 = db.prepare('DELETE FROM cases WHERE case_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, cs.case_id, cs.client_name, cs.industry, cs.challenge, cs.solution, cs.result,
cs.background, cs.enterprise_stage, cs.original_goal, cs.current_state, cs.surface_problem,
cs.amb_judgment, cs.key_obstacle, cs.root_cause, cs.evolution_path, cs.implementation, cs.case_significance
FROM contents c
LEFT JOIN cases cs ON c.content_id = cs.case_id
WHERE c.content_type = 'case'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'client_name' || key === 'industry' || key === 'challenge' || key === 'solution' || key === 'result' ||
key === 'background' || key === 'enterprise_stage' || key === 'original_goal' || key === 'current_state' ||
key === 'surface_problem' || key === 'amb_judgment' || key === 'key_obstacle' || key === 'root_cause' ||
key === 'evolution_path' || key === 'implementation' || key === 'case_significance') {
return `cs.${key} = ?`;
}
return `c.${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 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 = Case;
+229
View File
@@ -0,0 +1,229 @@
const { getDb } = require('../config/database');
const { v4: uuidv4 } = require('uuid');
class Content {
constructor(data = {}) {
this.content_id = data.content_id || uuidv4();
this.title = data.title;
this.body = data.body || null;
this.content_type = data.content_type;
this.publish_status = data.publish_status || 'draft';
this.authority_level = data.authority_level || 1;
this.version_no = data.version_no || 1;
this.version_note = data.version_note || null;
this.metadata = data.metadata ? JSON.stringify(data.metadata) : null;
this.created_at = data.created_at || new Date().toISOString();
this.updated_at = data.updated_at || new Date().toISOString();
this.archived_at = data.archived_at || null;
}
static create(data) {
const db = getDb();
const content = new Content(data);
const stmt = db.prepare(`
INSERT INTO contents (
content_id, title, body, content_type, publish_status,
authority_level, version_no, version_note, metadata,
created_at, updated_at, archived_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
content.content_id,
content.title,
content.body,
content.content_type,
content.publish_status,
content.authority_level,
content.version_no,
content.version_note,
content.metadata,
content.created_at,
content.updated_at,
content.archived_at
);
return content;
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM contents WHERE content_id = ?');
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static findByType(contentType, options = {}) {
const db = getDb();
let query = 'SELECT * FROM contents WHERE content_type = ?';
const params = [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 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 findPublished(options = {}) {
const db = getDb();
let query = 'SELECT * FROM contents WHERE publish_status = ?';
const params = ['published'];
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 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 update(contentId, data) {
const db = getDb();
const updateData = { ...data };
delete updateData.content_id;
delete updateData.created_at;
updateData.updated_at = new Date().toISOString();
if (updateData.metadata) {
updateData.metadata = JSON.stringify(updateData.metadata);
}
const setClause = Object.keys(updateData).map(key => `${key} = ?`).join(', ');
const values = Object.values(updateData);
const stmt = db.prepare(`UPDATE contents SET ${setClause} WHERE content_id = ?`);
stmt.run(...values, contentId);
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
const stmt = db.prepare('DELETE FROM contents WHERE content_id = ?');
return stmt.run(contentId);
}
static count() {
const db = getDb();
const stmt = db.prepare('SELECT COUNT(*) as count FROM contents');
const row = stmt.get();
return row.count;
}
// 内容状态流转
static transitionStatus(contentId, newStatus) {
const validTransitions = {
'draft': ['review'],
'review': ['draft', 'published'],
'published': ['unpublished'],
'unpublished': ['historical', 'published'],
'historical': []
};
const content = this.findById(contentId);
if (!content) {
throw new Error('内容不存在');
}
const allowedTransitions = validTransitions[content.publish_status];
if (!allowedTransitions.includes(newStatus)) {
throw new Error(`不允许从 ${content.publish_status} 转换到 ${newStatus}`);
}
const updateData = {
publish_status: newStatus,
updated_at: new Date().toISOString()
};
// 如果转换到historical,记录归档时间
if (newStatus === 'historical') {
updateData.archived_at = new Date().toISOString();
}
// 如果转换到published,增加版本号
if (newStatus === 'published') {
updateData.version_no = content.version_no + 1;
}
return this.update(contentId, updateData);
}
// 创建历史版本
static createHistoricalVersion(contentId) {
const content = this.findById(contentId);
if (!content) {
throw new Error('内容不存在');
}
// 创建新版本
const newData = {
...content,
content_id: uuidv4(),
version_no: content.version_no + 1,
publish_status: 'historical',
archived_at: new Date().toISOString(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
return this.create(newData);
}
}
module.exports = Content;
+145
View File
@@ -0,0 +1,145 @@
const { getDb } = require('../config/database');
const { v4: uuidv4 } = require('uuid');
class DirectedRelationship {
constructor(data = {}) {
this.rel_id = data.rel_id || uuidv4();
this.source_entity_id = data.source_entity_id;
this.relation_type = data.relation_type;
this.target_entity_id = data.target_entity_id;
this.rel_status = data.rel_status || 'active';
this.authority_level = data.authority_level || 1;
this.is_public = data.is_public || 0;
this.effective_at = data.effective_at || null;
this.expire_at = data.expire_at || null;
this.created_at = data.created_at || new Date().toISOString();
this.updated_at = data.updated_at || new Date().toISOString();
}
static create(data) {
const db = getDb();
const relationship = new DirectedRelationship(data);
const stmt = db.prepare(`
INSERT INTO directed_relationships (
rel_id, source_entity_id, relation_type, target_entity_id,
rel_status, authority_level, is_public, effective_at, expire_at,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
relationship.rel_id,
relationship.source_entity_id,
relationship.relation_type,
relationship.target_entity_id,
relationship.rel_status,
relationship.authority_level,
relationship.is_public,
relationship.effective_at,
relationship.expire_at,
relationship.created_at,
relationship.updated_at
);
return relationship;
}
static findById(relId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM directed_relationships WHERE rel_id = ?');
return stmt.get(relId);
}
static findBySourceEntity(sourceEntityId, options = {}) {
const db = getDb();
let query = 'SELECT * FROM directed_relationships WHERE source_entity_id = ?';
const params = [sourceEntityId];
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` AND ${conditions.join(' AND ')}`;
params.push(...Object.values(options.where));
}
const stmt = db.prepare(query);
return stmt.all(...params);
}
static findByTargetEntity(targetEntityId, options = {}) {
const db = getDb();
let query = 'SELECT * FROM directed_relationships WHERE target_entity_id = ?';
const params = [targetEntityId];
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` AND ${conditions.join(' AND ')}`;
params.push(...Object.values(options.where));
}
const stmt = db.prepare(query);
return stmt.all(...params);
}
static findAll(options = {}) {
const db = getDb();
let query = 'SELECT * FROM directed_relationships';
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` WHERE ${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 stmt = db.prepare(query);
return stmt.all(...params);
}
static update(relId, data) {
const db = getDb();
const updateData = { ...data };
delete updateData.rel_id;
delete updateData.created_at;
updateData.updated_at = new Date().toISOString();
const setClause = Object.keys(updateData).map(key => `${key} = ?`).join(', ');
const values = Object.values(updateData);
const stmt = db.prepare(`UPDATE directed_relationships SET ${setClause} WHERE rel_id = ?`);
stmt.run(...values, relId);
return this.findById(relId);
}
static delete(relId) {
const db = getDb();
const stmt = db.prepare('DELETE FROM directed_relationships WHERE rel_id = ?');
return stmt.run(relId);
}
static count() {
const db = getDb();
const stmt = db.prepare('SELECT COUNT(*) as count FROM directed_relationships');
const row = stmt.get();
return row.count;
}
}
module.exports = DirectedRelationship;
+134
View File
@@ -0,0 +1,134 @@
const { getDb } = require('../config/database');
const { v4: uuidv4 } = require('uuid');
class Entity {
constructor(data = {}) {
this.entity_id = data.entity_id || uuidv4();
this.entity_type = data.entity_type || 'concept';
this.name = data.name;
this.definition = data.definition || null;
this.description = data.description || null;
this.status = data.status || 'active';
this.public_flag = data.public_flag !== undefined ? data.public_flag : 1;
this.authority_level = data.authority_level || 1;
this.metadata = data.metadata ? JSON.stringify(data.metadata) : null;
this.created_at = data.created_at || new Date().toISOString();
this.updated_at = data.updated_at || new Date().toISOString();
}
static create(data) {
const db = getDb();
const entity = new Entity(data);
const stmt = db.prepare(`
INSERT INTO entities (entity_id, entity_type, name, definition, description, status, public_flag, authority_level, metadata, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
entity.entity_id,
entity.entity_type,
entity.name,
entity.definition,
entity.description,
entity.status,
entity.public_flag,
entity.authority_level,
entity.metadata,
entity.created_at,
entity.updated_at
);
return entity;
}
static findById(entityId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM entities WHERE entity_id = ?');
const row = stmt.get(entityId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static findAll(options = {}) {
const db = getDb();
let query = 'SELECT * FROM entities';
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` WHERE ${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 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 update(entityId, data) {
const db = getDb();
// 禁止修改entity_id
if (data.entity_id && data.entity_id !== entityId) {
throw new Error('不能修改entity_id');
}
const updateData = { ...data };
delete updateData.entity_id;
delete updateData.created_at;
updateData.updated_at = new Date().toISOString();
if (updateData.metadata) {
updateData.metadata = JSON.stringify(updateData.metadata);
}
const setClause = Object.keys(updateData).map(key => `${key} = ?`).join(', ');
const values = Object.values(updateData);
const stmt = db.prepare(`UPDATE entities SET ${setClause} WHERE entity_id = ?`);
stmt.run(...values, entityId);
return this.findById(entityId);
}
static delete(entityId) {
const db = getDb();
const stmt = db.prepare('DELETE FROM entities WHERE entity_id = ?');
return stmt.run(entityId);
}
static count() {
const db = getDb();
const stmt = db.prepare('SELECT COUNT(*) as count FROM entities');
const row = stmt.get();
return row.count;
}
}
module.exports = Entity;
+116
View File
@@ -0,0 +1,116 @@
const { getDb } = require('../config/database');
const { v4: uuidv4 } = require('uuid');
class EntityContentJoin {
constructor(data = {}) {
this.join_id = data.join_id || uuidv4();
this.entity_id = data.entity_id;
this.content_id = data.content_id;
this.join_note = data.join_note || null;
this.created_at = data.created_at || new Date().toISOString();
}
static create(data) {
const db = getDb();
const join = new EntityContentJoin(data);
const stmt = db.prepare(`
INSERT INTO entity_content_joins (join_id, entity_id, content_id, join_note, created_at)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(
join.join_id,
join.entity_id,
join.content_id,
join.join_note,
join.created_at
);
return join;
}
static findById(joinId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM entity_content_joins WHERE join_id = ?');
return stmt.get(joinId);
}
static findByEntityId(entityId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM entity_content_joins WHERE entity_id = ?');
return stmt.all(entityId);
}
static findByContentId(contentId) {
const db = getDb();
const stmt = db.prepare('SELECT * FROM entity_content_joins WHERE content_id = ?');
return stmt.all(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = 'SELECT * FROM entity_content_joins';
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => `${key} = ?`);
query += ` WHERE ${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 stmt = db.prepare(query);
return stmt.all(...params);
}
static update(joinId, data) {
const db = getDb();
const updateData = { ...data };
delete updateData.join_id;
delete updateData.created_at;
const setClause = Object.keys(updateData).map(key => `${key} = ?`).join(', ');
const values = Object.values(updateData);
const stmt = db.prepare(`UPDATE entity_content_joins SET ${setClause} WHERE join_id = ?`);
stmt.run(...values, joinId);
return this.findById(joinId);
}
static delete(joinId) {
const db = getDb();
const stmt = db.prepare('DELETE FROM entity_content_joins WHERE join_id = ?');
return stmt.run(joinId);
}
static deleteByEntityAndContent(entityId, contentId) {
const db = getDb();
const stmt = db.prepare('DELETE FROM entity_content_joins WHERE entity_id = ? AND content_id = ?');
return stmt.run(entityId, contentId);
}
static count() {
const db = getDb();
const stmt = db.prepare('SELECT COUNT(*) as count FROM entity_content_joins');
const row = stmt.get();
return row.count;
}
}
module.exports = EntityContentJoin;
+145
View File
@@ -0,0 +1,145 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class FAQ extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'faq';
this.faq_id = data.faq_id || this.content_id;
this.question = data.question || null;
this.answer = data.answer || null;
this.sort_order = data.sort_order || 0;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'faq'
};
const content = super.create(contentData);
// 再创建FAQ扩展记录
const stmt = db.prepare(`
INSERT INTO faqs (faq_id, question, answer, sort_order)
VALUES (?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.question || null,
data.answer || null,
data.sort_order || 0
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, f.faq_id, f.question, f.answer, f.sort_order
FROM contents c
LEFT JOIN faqs f ON c.content_id = f.faq_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.faq_id;
delete contentData.question;
delete contentData.answer;
delete contentData.sort_order;
super.update(contentId, contentData);
// 更新FAQ扩展部分
const faqData = {};
if (data.question !== undefined) faqData.question = data.question;
if (data.answer !== undefined) faqData.answer = data.answer;
if (data.sort_order !== undefined) faqData.sort_order = data.sort_order;
if (Object.keys(faqData).length > 0) {
const setClause = Object.keys(faqData).map(key => `${key} = ?`).join(', ');
const values = Object.values(faqData);
const stmt = db.prepare(`UPDATE faqs SET ${setClause} WHERE faq_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除FAQ扩展记录
const stmt1 = db.prepare('DELETE FROM faqs WHERE faq_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, f.faq_id, f.question, f.answer, f.sort_order
FROM contents c
LEFT JOIN faqs f ON c.content_id = f.faq_id
WHERE c.content_type = 'faq'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'question' || key === 'answer' || key === 'sort_order') {
return `f.${key} = ?`;
}
return `c.${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 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 = FAQ;
+174
View File
@@ -0,0 +1,174 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class Page extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'page';
this.page_id = data.page_id || this.content_id;
this.slug = data.slug || null;
this.template = data.template || null;
this.summary = data.summary || null;
this.seo_title = data.seo_title || null;
this.seo_description = data.seo_description || null;
this.seo_keywords = data.seo_keywords || null;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'page'
};
const content = super.create(contentData);
// 再创建Page扩展记录
const stmt = db.prepare(`
INSERT INTO pages (page_id, slug, template, summary, seo_title, seo_description, seo_keywords)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.slug || null,
data.template || null,
data.summary || null,
data.seo_title || null,
data.seo_description || null,
data.seo_keywords || null
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, p.page_id, p.slug, p.template, p.summary, p.seo_title, p.seo_description, p.seo_keywords
FROM contents c
LEFT JOIN pages p ON c.content_id = p.page_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static findBySlug(slug) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, p.page_id, p.slug, p.template, p.summary, p.seo_title, p.seo_description, p.seo_keywords
FROM contents c
LEFT JOIN pages p ON c.content_id = p.page_id
WHERE p.slug = ?
`);
const row = stmt.get(slug);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.page_id;
delete contentData.slug;
delete contentData.template;
delete contentData.summary;
delete contentData.seo_title;
delete contentData.seo_description;
delete contentData.seo_keywords;
super.update(contentId, contentData);
// 更新Page扩展部分
const pageData = {};
if (data.slug !== undefined) pageData.slug = data.slug;
if (data.template !== undefined) pageData.template = data.template;
if (data.summary !== undefined) pageData.summary = data.summary;
if (data.seo_title !== undefined) pageData.seo_title = data.seo_title;
if (data.seo_description !== undefined) pageData.seo_description = data.seo_description;
if (data.seo_keywords !== undefined) pageData.seo_keywords = data.seo_keywords;
if (Object.keys(pageData).length > 0) {
const setClause = Object.keys(pageData).map(key => `${key} = ?`).join(', ');
const values = Object.values(pageData);
const stmt = db.prepare(`UPDATE pages SET ${setClause} WHERE page_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除Page扩展记录
const stmt1 = db.prepare('DELETE FROM pages WHERE page_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, p.page_id, p.slug, p.template, p.summary, p.seo_title, p.seo_description, p.seo_keywords
FROM contents c
LEFT JOIN pages p ON c.content_id = p.page_id
WHERE c.content_type = 'page'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'slug' || key === 'template' || key === 'summary' || key === 'seo_title' || key === 'seo_description' || key === 'seo_keywords') {
return `p.${key} = ?`;
}
return `c.${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 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 = Page;
+145
View File
@@ -0,0 +1,145 @@
const { getDb } = require('../config/database');
const Content = require('./Content');
class Service extends Content {
constructor(data = {}) {
super(data);
this.content_type = 'service';
this.service_id = data.service_id || this.content_id;
this.service_type = data.service_type || null;
this.pricing_model = data.pricing_model || null;
this.delivery_method = data.delivery_method || null;
}
static create(data) {
const db = getDb();
// 先创建Content记录
const contentData = {
...data,
content_type: 'service'
};
const content = super.create(contentData);
// 再创建Service扩展记录
const stmt = db.prepare(`
INSERT INTO services (service_id, service_type, pricing_model, delivery_method)
VALUES (?, ?, ?, ?)
`);
stmt.run(
content.content_id,
data.service_type || null,
data.pricing_model || null,
data.delivery_method || null
);
return this.findById(content.content_id);
}
static findById(contentId) {
const db = getDb();
const stmt = db.prepare(`
SELECT c.*, s.service_id, s.service_type, s.pricing_model, s.delivery_method
FROM contents c
LEFT JOIN services s ON c.content_id = s.service_id
WHERE c.content_id = ?
`);
const row = stmt.get(contentId);
if (row && row.metadata) {
row.metadata = JSON.parse(row.metadata);
}
return row;
}
static update(contentId, data) {
const db = getDb();
// 更新Content部分
const contentData = { ...data };
delete contentData.service_id;
delete contentData.service_type;
delete contentData.pricing_model;
delete contentData.delivery_method;
super.update(contentId, contentData);
// 更新Service扩展部分
const serviceData = {};
if (data.service_type !== undefined) serviceData.service_type = data.service_type;
if (data.pricing_model !== undefined) serviceData.pricing_model = data.pricing_model;
if (data.delivery_method !== undefined) serviceData.delivery_method = data.delivery_method;
if (Object.keys(serviceData).length > 0) {
const setClause = Object.keys(serviceData).map(key => `${key} = ?`).join(', ');
const values = Object.values(serviceData);
const stmt = db.prepare(`UPDATE services SET ${setClause} WHERE service_id = ?`);
stmt.run(...values, contentId);
}
return this.findById(contentId);
}
static delete(contentId) {
const db = getDb();
// 删除Service扩展记录
const stmt1 = db.prepare('DELETE FROM services WHERE service_id = ?');
stmt1.run(contentId);
// 删除Content记录
return super.delete(contentId);
}
static findAll(options = {}) {
const db = getDb();
let query = `
SELECT c.*, s.service_id, s.service_type, s.pricing_model, s.delivery_method
FROM contents c
LEFT JOIN services s ON c.content_id = s.service_id
WHERE c.content_type = 'service'
`;
const params = [];
if (options.where) {
const conditions = Object.keys(options.where).map(key => {
if (key === 'service_type' || key === 'pricing_model' || key === 'delivery_method') {
return `s.${key} = ?`;
}
return `c.${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 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 = Service;
+98
View File
@@ -0,0 +1,98 @@
const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
const AdminAuthService = require('../services/AdminAuthService');
const ContentService = require('../services/ContentService');
const { getDb } = require('../config/database');
const auth = (req, res, next) => {
const header = req.headers.authorization || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
const session = AdminAuthService.verify(token);
if (!session) return res.status(401).json({ error: '请先登录管理后台' });
req.admin = session;
req.adminToken = token;
next();
};
router.post('/login', (req, res) => {
try {
const { username, password } = req.body;
res.json(AdminAuthService.login(username, password));
} catch (error) {
res.status(401).json({ error: error.message });
}
});
router.use(auth);
router.get('/me', (req, res) => res.json({ username: req.admin.username }));
router.post('/logout', (req, res) => {
AdminAuthService.logout(req.adminToken);
res.json({ success: true });
});
router.put('/password', (req, res) => {
try {
const { currentPassword, newPassword } = req.body;
res.json(AdminAuthService.changePassword(req.admin.username, currentPassword, newPassword));
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.get('/settings', (req, res) => {
const rows = getDb().prepare('SELECT * FROM site_settings ORDER BY setting_group, setting_key').all();
res.json(rows);
});
router.put('/settings/:key', (req, res) => {
const key = req.params.key;
const value = req.body.value === undefined ? '' : String(req.body.value);
const group = req.body.group || 'general';
const db = getDb();
const existing = db.prepare('SELECT setting_key FROM site_settings WHERE setting_key = ?').get(key);
if (existing) {
db.prepare('UPDATE site_settings SET setting_value = ?, setting_group = ?, updated_at = CURRENT_TIMESTAMP WHERE setting_key = ?').run(value, group, key);
} else {
db.prepare('INSERT INTO site_settings (setting_key, setting_value, setting_group) VALUES (?, ?, ?)').run(key, value, group);
}
res.json({ setting_key: key, setting_value: value, setting_group: group });
});
router.get('/contents/:type', (req, res) => {
try {
const { page = 1, pageSize = 50 } = req.query;
res.json(ContentService.paginate(req.params.type, Number(page), Number(pageSize)));
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.post('/contents/:type', (req, res) => {
try {
res.status(201).json(ContentService.create(req.body, req.params.type));
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.put('/contents/:type/:id', (req, res) => {
try {
res.json(ContentService.update(req.params.id, req.body, req.params.type));
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.delete('/contents/:type/:id', (req, res) => {
try {
ContentService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
+91
View File
@@ -0,0 +1,91 @@
const express = require('express');
const router = express.Router();
const ContentService = require('../services/ContentService');
// 获取内容列表(根据类型)
router.get('/:type', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const result = ContentService.paginate(req.params.type, parseInt(page), parseInt(pageSize), { orderBy });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取已发布的内容列表(根据类型)
router.get('/:type/published', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const result = ContentService.paginate(req.params.type, parseInt(page), parseInt(pageSize), {
orderBy,
where: { publish_status: 'published' }
});
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取内容详情
router.get('/:type/:id', async (req, res) => {
try {
const content = ContentService.getById(req.params.id, req.params.type);
res.json(content);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 创建内容
router.post('/:type', async (req, res) => {
try {
const content = ContentService.create(req.body, req.params.type);
res.status(201).json(content);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 更新内容
router.put('/:type/:id', async (req, res) => {
try {
const content = ContentService.update(req.params.id, req.body, req.params.type);
res.json(content);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 删除内容
router.delete('/:type/:id', async (req, res) => {
try {
ContentService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 内容状态流转
router.put('/:type/:id/transition', async (req, res) => {
try {
const { status } = req.body;
const content = ContentService.transitionStatus(req.params.id, status);
res.json(content);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 创建历史版本
router.post('/:type/:id/history', async (req, res) => {
try {
const content = ContentService.createHistoricalVersion(req.params.id);
res.status(201).json(content);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
+86
View File
@@ -0,0 +1,86 @@
const express = require('express');
const router = express.Router();
const EntityContentJoinService = require('../services/EntityContentJoinService');
// 获取关联列表
router.get('/', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const result = EntityContentJoinService.paginate(parseInt(page), parseInt(pageSize), { orderBy });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取关联详情
router.get('/:id', async (req, res) => {
try {
const join = EntityContentJoinService.getById(req.params.id);
res.json(join);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 获取实体的所有关联
router.get('/entity/:entityId', async (req, res) => {
try {
const joins = EntityContentJoinService.getByEntityId(req.params.entityId);
res.json(joins);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取内容的所有关联
router.get('/content/:contentId', async (req, res) => {
try {
const joins = EntityContentJoinService.getByContentId(req.params.contentId);
res.json(joins);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 创建关联
router.post('/', async (req, res) => {
try {
const join = EntityContentJoinService.create(req.body);
res.status(201).json(join);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 更新关联
router.put('/:id', async (req, res) => {
try {
const join = EntityContentJoinService.update(req.params.id, req.body);
res.json(join);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 删除关联
router.delete('/:id', async (req, res) => {
try {
EntityContentJoinService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 删除实体与内容的关联
router.delete('/entity/:entityId/content/:contentId', async (req, res) => {
try {
EntityContentJoinService.deleteByEntityAndContent(req.params.entityId, req.params.contentId);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
+56
View File
@@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const EntityService = require('../services/EntityService');
// 获取实体列表
router.get('/', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const result = EntityService.paginate(parseInt(page), parseInt(pageSize), { orderBy });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取实体详情
router.get('/:id', async (req, res) => {
try {
const entity = EntityService.getById(req.params.id);
res.json(entity);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 创建实体
router.post('/', async (req, res) => {
try {
const entity = EntityService.create(req.body);
res.status(201).json(entity);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 更新实体
router.put('/:id', async (req, res) => {
try {
const entity = EntityService.update(req.params.id, req.body);
res.json(entity);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 删除实体
router.delete('/:id', async (req, res) => {
try {
EntityService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
+56
View File
@@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const JsonLdService = require('../services/JsonLdService');
// 获取实体的JSON-LD
router.get('/entities/:id', async (req, res) => {
try {
const jsonLd = JsonLdService.generateEntityJsonLd(req.params.id);
res.json(jsonLd);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 获取内容的JSON-LD
router.get('/contents/:id', async (req, res) => {
try {
const jsonLd = JsonLdService.generateContentJsonLd(req.params.id);
res.json(jsonLd);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 获取所有实体的JSON-LD列表
router.get('/entities', async (req, res) => {
try {
const jsonLdList = JsonLdService.generateAllEntitiesJsonLd();
res.json(jsonLdList);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取所有内容的JSON-LD列表
router.get('/contents', async (req, res) => {
try {
const { type } = req.query;
const jsonLdList = JsonLdService.generateAllContentsJsonLd(type);
res.json(jsonLdList);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取站点地图的JSON-LD
router.get('/sitemap', async (req, res) => {
try {
const jsonLd = JsonLdService.generateSitemapJsonLd();
res.json(jsonLd);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+149
View File
@@ -0,0 +1,149 @@
const express = require('express');
const router = express.Router();
const PublicApiService = require('../services/PublicApiService');
const { getDb } = require('../config/database');
// 获取前台展示配置(只读)
router.get('/settings', async (req, res) => {
try {
const rows = getDb().prepare('SELECT setting_key, setting_value FROM site_settings').all();
res.json(Object.fromEntries(rows.map(row => [row.setting_key, row.setting_value])));
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取已发布的实体列表
router.get('/entities', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const entities = PublicApiService.getPublishedEntities({
orderBy,
limit: parseInt(pageSize),
offset
});
res.json(entities);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取已发布的实体详情
router.get('/entities/:id', async (req, res) => {
try {
const entity = PublicApiService.getPublishedEntityById(req.params.id);
if (!entity) {
return res.status(404).json({ error: '实体不存在' });
}
res.json(entity);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取实体的已发布关系
router.get('/entities/:id/relationships', async (req, res) => {
try {
const sourceRelationships = PublicApiService.getPublishedRelationshipsBySource(req.params.id);
const targetRelationships = PublicApiService.getPublishedRelationshipsByTarget(req.params.id);
res.json({
source: sourceRelationships,
target: targetRelationships
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取实体的已发布内容
router.get('/entities/:id/contents', async (req, res) => {
try {
const contents = PublicApiService.getEntityPublishedContents(req.params.id);
res.json(contents);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取已发布的内容列表
router.get('/contents', async (req, res) => {
try {
const { type, page = 1, pageSize = 10, orderBy } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const contents = PublicApiService.getPublishedContents(type, {
orderBy,
limit: parseInt(pageSize),
offset
});
res.json(contents);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取已发布的内容详情
router.get('/contents/:id', async (req, res) => {
try {
const content = PublicApiService.getPublishedContentById(req.params.id);
if (!content) {
return res.status(404).json({ error: '内容不存在或未发布' });
}
res.json(content);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取内容的实体关联
router.get('/contents/:id/entities', async (req, res) => {
try {
const entities = PublicApiService.getContentEntities(req.params.id);
res.json(entities);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 搜索实体
router.get('/search/entities', async (req, res) => {
try {
const { keyword, page = 1, pageSize = 10, orderBy } = req.query;
if (!keyword) {
return res.status(400).json({ error: '搜索关键词不能为空' });
}
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const entities = PublicApiService.searchEntities(keyword, {
orderBy,
limit: parseInt(pageSize),
offset
});
res.json(entities);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 搜索内容
router.get('/search/contents', async (req, res) => {
try {
const { keyword, type, page = 1, pageSize = 10, orderBy } = req.query;
if (!keyword) {
return res.status(400).json({ error: '搜索关键词不能为空' });
}
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const contents = PublicApiService.searchContents(keyword, type, {
orderBy,
limit: parseInt(pageSize),
offset
});
res.json(contents);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+76
View File
@@ -0,0 +1,76 @@
const express = require('express');
const router = express.Router();
const DirectedRelationshipService = require('../services/DirectedRelationshipService');
// 获取关系列表
router.get('/', async (req, res) => {
try {
const { page = 1, pageSize = 10, orderBy } = req.query;
const result = DirectedRelationshipService.paginate(parseInt(page), parseInt(pageSize), { orderBy });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取关系详情
router.get('/:id', async (req, res) => {
try {
const relationship = DirectedRelationshipService.getById(req.params.id);
res.json(relationship);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 获取实体的出向关系
router.get('/source/:entityId', async (req, res) => {
try {
const relationships = DirectedRelationshipService.getBySourceEntity(req.params.entityId);
res.json(relationships);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取实体的入向关系
router.get('/target/:entityId', async (req, res) => {
try {
const relationships = DirectedRelationshipService.getByTargetEntity(req.params.entityId);
res.json(relationships);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 创建关系
router.post('/', async (req, res) => {
try {
const relationship = DirectedRelationshipService.create(req.body);
res.status(201).json(relationship);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 更新关系
router.put('/:id', async (req, res) => {
try {
const relationship = DirectedRelationshipService.update(req.params.id, req.body);
res.json(relationship);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// 删除关系
router.delete('/:id', async (req, res) => {
try {
DirectedRelationshipService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
+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;