diff --git a/init-forum-es.js b/init-forum-es.js new file mode 100644 index 00000000..327ce550 --- /dev/null +++ b/init-forum-es.js @@ -0,0 +1,145 @@ +/** + * 初始化价值论坛 Elasticsearch 索引 + * 运行方式:node init-forum-es.js + */ + +const axios = require('axios'); + +// Elasticsearch 配置 +const ES_BASE_URL = 'http://222.128.1.157:19200'; + +// 创建 axios 实例 +const esClient = axios.create({ + baseURL: ES_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 索引名称 +const INDICES = { + POSTS: 'forum_posts', + COMMENTS: 'forum_comments', + EVENTS: 'forum_events', +}; + +async function initializeIndices() { + try { + console.log('开始初始化 Elasticsearch 索引...\n'); + + // 1. 创建帖子索引 + console.log('创建帖子索引 (forum_posts)...'); + try { + await esClient.put(`/${INDICES.POSTS}`, { + mappings: { + properties: { + id: { type: 'keyword' }, + author_id: { type: 'keyword' }, + author_name: { type: 'text' }, + author_avatar: { type: 'keyword' }, + title: { type: 'text' }, + content: { type: 'text' }, + images: { type: 'keyword' }, + tags: { type: 'keyword' }, + category: { type: 'keyword' }, + likes_count: { type: 'integer' }, + comments_count: { type: 'integer' }, + views_count: { type: 'integer' }, + created_at: { type: 'date' }, + updated_at: { type: 'date' }, + is_pinned: { type: 'boolean' }, + status: { type: 'keyword' }, + }, + }, + }); + console.log('✅ 帖子索引创建成功\n'); + } catch (error) { + if (error.response?.status === 400 && error.response?.data?.error?.type === 'resource_already_exists_exception') { + console.log('⚠️ 帖子索引已存在,跳过创建\n'); + } else { + throw error; + } + } + + // 2. 创建评论索引 + console.log('创建评论索引 (forum_comments)...'); + try { + await esClient.put(`/${INDICES.COMMENTS}`, { + mappings: { + properties: { + id: { type: 'keyword' }, + post_id: { type: 'keyword' }, + author_id: { type: 'keyword' }, + author_name: { type: 'text' }, + author_avatar: { type: 'keyword' }, + content: { type: 'text' }, + parent_id: { type: 'keyword' }, + likes_count: { type: 'integer' }, + created_at: { type: 'date' }, + status: { type: 'keyword' }, + }, + }, + }); + console.log('✅ 评论索引创建成功\n'); + } catch (error) { + if (error.response?.status === 400 && error.response?.data?.error?.type === 'resource_already_exists_exception') { + console.log('⚠️ 评论索引已存在,跳过创建\n'); + } else { + throw error; + } + } + + // 3. 创建事件时间轴索引 + console.log('创建事件时间轴索引 (forum_events)...'); + try { + await esClient.put(`/${INDICES.EVENTS}`, { + mappings: { + properties: { + id: { type: 'keyword' }, + post_id: { type: 'keyword' }, + event_type: { type: 'keyword' }, + title: { type: 'text' }, + description: { type: 'text' }, + source: { type: 'keyword' }, + source_url: { type: 'keyword' }, + related_stocks: { type: 'keyword' }, + occurred_at: { type: 'date' }, + created_at: { type: 'date' }, + importance: { type: 'keyword' }, + }, + }, + }); + console.log('✅ 事件时间轴索引创建成功\n'); + } catch (error) { + if (error.response?.status === 400 && error.response?.data?.error?.type === 'resource_already_exists_exception') { + console.log('⚠️ 事件时间轴索引已存在,跳过创建\n'); + } else { + throw error; + } + } + + // 4. 验证索引 + console.log('验证索引...'); + const indices = await esClient.get('/_cat/indices/forum_*?v&format=json'); + console.log('已创建的论坛索引:'); + indices.data.forEach(index => { + console.log(` - ${index.index} (docs: ${index['docs.count']}, size: ${index['store.size']})`); + }); + + console.log('\n🎉 所有索引初始化完成!'); + console.log('\n下一步:'); + console.log('1. 访问 https://valuefrontier.cn/value-forum'); + console.log('2. 点击"发布帖子"按钮创建第一篇帖子'); + + } catch (error) { + console.error('❌ 初始化失败:', error.message); + if (error.response) { + console.error('响应数据:', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +// 执行初始化 +initializeIndices(); diff --git a/scripts/init-forum-indices.sh b/scripts/init-forum-indices.sh new file mode 100644 index 00000000..7a81c34b --- /dev/null +++ b/scripts/init-forum-indices.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# 初始化价值论坛 Elasticsearch 索引 +# 使用 Nginx 代理或直连 ES + +set -e + +echo "🚀 开始初始化价值论坛 Elasticsearch 索引..." +echo "" + +# ES 地址(根据环境选择) +if [ -n "$USE_PROXY" ]; then + ES_URL="https://valuefrontier.cn/es-api" + echo "📡 使用 Nginx 代理: $ES_URL" +else + ES_URL="http://222.128.1.157:19200" + echo "📡 直连 Elasticsearch: $ES_URL" +fi +echo "" + +# 1. 创建帖子索引 +echo "📝 创建帖子索引 (forum_posts)..." +curl -X PUT "$ES_URL/forum_posts" -H 'Content-Type: application/json' -d '{ + "mappings": { + "properties": { + "id": { "type": "keyword" }, + "author_id": { "type": "keyword" }, + "author_name": { "type": "text" }, + "author_avatar": { "type": "keyword" }, + "title": { "type": "text" }, + "content": { "type": "text" }, + "images": { "type": "keyword" }, + "tags": { "type": "keyword" }, + "category": { "type": "keyword" }, + "likes_count": { "type": "integer" }, + "comments_count": { "type": "integer" }, + "views_count": { "type": "integer" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "is_pinned": { "type": "boolean" }, + "status": { "type": "keyword" } + } + } +}' 2>/dev/null && echo "✅ 帖子索引创建成功" || echo "⚠️ 帖子索引已存在或创建失败" +echo "" + +# 2. 创建评论索引 +echo "💬 创建评论索引 (forum_comments)..." +curl -X PUT "$ES_URL/forum_comments" -H 'Content-Type: application/json' -d '{ + "mappings": { + "properties": { + "id": { "type": "keyword" }, + "post_id": { "type": "keyword" }, + "author_id": { "type": "keyword" }, + "author_name": { "type": "text" }, + "author_avatar": { "type": "keyword" }, + "content": { "type": "text" }, + "parent_id": { "type": "keyword" }, + "likes_count": { "type": "integer" }, + "created_at": { "type": "date" }, + "status": { "type": "keyword" } + } + } +}' 2>/dev/null && echo "✅ 评论索引创建成功" || echo "⚠️ 评论索引已存在或创建失败" +echo "" + +# 3. 创建事件时间轴索引 +echo "⏰ 创建事件时间轴索引 (forum_events)..." +curl -X PUT "$ES_URL/forum_events" -H 'Content-Type: application/json' -d '{ + "mappings": { + "properties": { + "id": { "type": "keyword" }, + "post_id": { "type": "keyword" }, + "event_type": { "type": "keyword" }, + "title": { "type": "text" }, + "description": { "type": "text" }, + "source": { "type": "keyword" }, + "source_url": { "type": "keyword" }, + "related_stocks": { "type": "keyword" }, + "occurred_at": { "type": "date" }, + "created_at": { "type": "date" }, + "importance": { "type": "keyword" } + } + } +}' 2>/dev/null && echo "✅ 事件时间轴索引创建成功" || echo "⚠️ 事件时间轴索引已存在或创建失败" +echo "" + +# 4. 验证索引 +echo "🔍 验证已创建的索引..." +curl -X GET "$ES_URL/_cat/indices/forum_*?v" 2>/dev/null +echo "" + +echo "🎉 初始化完成!" +echo "" +echo "下一步:" +echo " 1. 访问 https://valuefrontier.cn/value-forum" +echo " 2. 点击"发布帖子"按钮创建第一篇帖子" diff --git a/src/services/elasticsearchService.js b/src/services/elasticsearchService.js index f346b1fe..17a70637 100644 --- a/src/services/elasticsearchService.js +++ b/src/services/elasticsearchService.js @@ -349,6 +349,11 @@ export const getCommentsByPostId = async (postId, { page = 1, size = 50 }) => { comments: response.data.hits.hits.map((hit) => ({ ...hit._source, _id: hit._id })), }; } catch (error) { + // 如果索引不存在(404),返回空结果 + if (error.response?.status === 404) { + console.warn('评论索引不存在,返回空结果:', INDICES.COMMENTS); + return { total: 0, comments: [] }; + } console.error('获取评论列表失败:', error); throw error; } @@ -407,6 +412,11 @@ export const getEventsByPostId = async (postId) => { return response.data.hits.hits.map((hit) => ({ ...hit._source, _id: hit._id })); } catch (error) { + // 如果索引不存在(404),返回空数组而不是抛出错误 + if (error.response?.status === 404) { + console.warn('事件索引不存在,返回空数组:', INDICES.EVENTS); + return []; + } console.error('获取事件时间轴失败:', error); throw error; }