Files
AOUN/scripts/seed.ts
2025-11-28 22:44:54 +08:00

479 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 作者: 阿瑞
* 功能: 数据库填充脚本 (Seeding Script)
* 版本: 2.0.0
*
* 运行方式: npx tsx scripts/seed.ts
*/
import mongoose from 'mongoose';
import fs from 'fs';
import path from 'path';
import bcrypt from 'bcryptjs';
import {
User,
Article,
SystemConfig,
Comment,
Order,
MembershipPlan,
Category,
Tag
} from '../src/models/index';
// ------------------------------------------------------------------
// 1. 环境配置加载 (手动读取 .env.local避免依赖 dotenv)
// ------------------------------------------------------------------
const loadEnv = () => {
const envPath = path.resolve(process.cwd(), '.env.local');
if (fs.existsSync(envPath)) {
console.log('📄 正在加载 .env.local 配置...');
const envConfig = fs.readFileSync(envPath, 'utf8');
envConfig.split('\n').forEach((line) => {
const parts = line.split('=');
if (parts.length >= 2) {
const key = parts[0].trim();
const value = parts.slice(1).join('=').trim();
if (key && value && !key.startsWith('#')) {
process.env[key] = value;
}
}
});
} else {
console.warn('⚠️ 未找到 .env.local 文件,尝试使用系统环境变量...');
}
};
loadEnv();
const MONGODB_URI = process.env.MONGODB_URI;
if (!MONGODB_URI) {
console.error('❌ 错误: MONGODB_URI 未定义,请检查 .env.local 文件');
process.exit(1);
}
// ------------------------------------------------------------------
// 2. 辅助函数
// ------------------------------------------------------------------
const getRandomInt = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min;
const getRandomItem = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
// ------------------------------------------------------------------
// 3. 主逻辑
// ------------------------------------------------------------------
const seed = async () => {
try {
console.log('🔌 正在连接数据库...');
await mongoose.connect(MONGODB_URI);
console.log('✅ 数据库连接成功');
console.log('🧹 正在清理旧数据...');
await Promise.all([
User.deleteMany({}),
Article.deleteMany({}),
SystemConfig.deleteMany({}),
Comment.deleteMany({}),
Order.deleteMany({}),
MembershipPlan.deleteMany({}),
Category.deleteMany({}),
Tag.deleteMany({})
]);
console.log('✅ 旧数据清理完成');
// --------------------------------------------------------------
// 4. 创建基础数据
// --------------------------------------------------------------
// --- 4.1 系统配置 ---
console.log('⚙️ 创建系统配置...');
await SystemConfig.create({
: 'default',
: {
: 'AOUN - 独立开发者资源站',
: '构建你的数字资产',
Logo地址: '/LOGO.png',
SEO关键词: 'React, Next.js, AI, 独立开发, 源码下载',
SEO描述: 'AOUN 是一个专注于分享高质量编程资源、AI 实战教程和独立开发经验的平台。',
: '© 2025 AounApp. All Rights Reserved.'
},
Banner配置: [
{
: 'Next.js 15 全栈实战',
: '深入探索 Next.js 15 的最新特性,从 App Router 到 Server Actions构建高性能全栈应用。',
: 'https://images.unsplash.com/photo-1618477247222-ac59124545da?q=80&w=2070&auto=format&fit=crop',
: '立即学习',
: '/article/nextjs-15-fullstack',
: 'visible'
},
{
: 'AI Agent 开发指南',
: '掌握 LLM 应用开发核心,使用 LangChain 和 OpenAI 构建智能体。',
: 'https://images.unsplash.com/photo-1677442136019-21780ecad995?q=80&w=2070&auto=format&fit=crop',
: '查看教程',
: '/article/ai-agent-guide',
: 'visible'
},
{
: '独立开发变现之路',
: '分享从 0 到 1 的产品构建、推广与商业化经验。',
: 'https://images.unsplash.com/photo-1553729459-efe14ef6055d?q=80&w=2070&auto=format&fit=crop',
: '阅读专栏',
: '/category/indie-hacker',
: 'visible'
}
],
AI配置列表: [
{
: '写作助手-Gemini',
: 'https://generativelanguage.googleapis.com/v1beta/openai',
API密钥: process.env.GEMINI_API_KEY || 'dummy-key',
: 'gemini-1.5-flash',
: '你是一个专业的技术写作助手,擅长撰写清晰、有深度的技术文章。',
是否启用: true,
流式传输: true
}
]
});
// --- 4.2 会员套餐 ---
console.log('💎 创建会员套餐...');
const plans = await MembershipPlan.create([
{
: '月度会员',
有效天数: 30,
价格: 29.9,
: '适合短期突击学习,畅享一个月无限下载',
: { 每日下载限制: 20, 购买折扣: 0.9 }
},
{
: '年度会员',
有效天数: 365,
价格: 299,
: '超值推荐,全年无忧学习,低至 0.8 元/天',
: { 每日下载限制: 100, 购买折扣: 0.7 }
},
{
: '永久会员',
有效天数: 36500,
价格: 999,
: '一次付费,终身受益,尊享所有未来更新',
: { 每日下载限制: 9999, 购买折扣: 0.5 }
}
]);
// --- 4.3 分类与标签 ---
console.log('🏷️ 创建分类与标签...');
const categories = await Category.create([
{ : '全栈开发', : 'fullstack', 排序权重: 10 },
{ : 'AI 工程化', : 'ai-engineering', 排序权重: 9 },
{ : '系统设计', : 'system-design', 排序权重: 8 },
{ : '独立开发', : 'indie-hacker', 排序权重: 7 },
{ : '源码资源', : 'resources', 排序权重: 6 }
]);
const tags = await Tag.create([
{ : 'Next.js' }, { : 'React' }, { : 'TypeScript' },
{ : 'TailwindCSS' }, { : 'Node.js' }, { : 'PostgreSQL' },
{ : 'MongoDB' }, { : 'Docker' }, { : 'LangChain' },
{ : 'OpenAI' }, { : 'RAG' }, { : 'SaaS' }
]);
// --- 4.4 用户 ---
console.log('👥 创建用户...');
// 加密密码
const adminPassword = await bcrypt.hash('admin@aoun.ltd', 10);
const userPassword = await bcrypt.hash('123456', 10);
const adminUser = await User.create({
: 'Admin',
: 'admin@aoun.ltd',
密码: adminPassword,
: 'admin',
: 'https://api.dicebear.com/7.x/avataaars/svg?seed=admin',
: { 当前积分: 99999, 历史总消费: 0 }
});
const demoUsers = await User.create([
{
: 'Alex',
: 'alex@example.com',
密码: userPassword,
: 'user',
: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alex',
: { 当前积分: 50, 历史总消费: 100 }
},
{
: 'Sarah',
: 'sarah@example.com',
密码: userPassword,
: 'user',
: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah',
: {
当前等级ID: plans[1]._id, // 年度会员
过期时间: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
}
},
{
: 'Developer',
: 'dev@example.com',
密码: userPassword,
: 'editor',
: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Dev'
}
]);
const allUsers = [adminUser, ...demoUsers];
// --- 4.5 文章/资源 ---
console.log('📚 创建文章与资源...');
const articlesData = [
{
title: 'Next.js 15 App Router 深度解析与最佳实践',
summary: '全面解析 Next.js 15 的 App Router 架构,探讨 Server Components、Server Actions 以及流式渲染的最佳实践。',
content: `
## 引言
Next.js 15 带来了革命性的变化App Router 彻底改变了我们构建 React 应用的方式。本文将带你深入了解其核心概念。
## Server Components
React Server Components (RSC) 允许我们在服务器端渲染组件,减少发送到客户端的 JavaScript 体积。
\`\`\`tsx
async function GetData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default async function Page() {
const data = await GetData();
return <main>{data.title}</main>;
}
\`\`\`
## Server Actions
不再需要单独的 API 路由,直接在组件中定义服务器端逻辑。
## 总结
掌握 App Router 是成为现代 React 全栈开发者的关键。
`,
category: '全栈开发',
tags: ['Next.js', 'React', 'TypeScript'],
isPaid: false
},
{
title: '构建企业级 RAG 应用:从原理到实战',
summary: '基于 LangChain 和 Vector Database手把手教你搭建一个能够理解私有数据的 AI 知识库问答系统。',
content: `
## 什么是 RAG
检索增强生成 (Retrieval-Augmented Generation) 是解决 LLM 幻觉和知识过时的有效方案。
## 架构设计
1. **文档加载与切分**:使用 LangChain Loader。
2. **向量化 (Embedding)**:使用 OpenAI Embedding 模型。
3. **向量数据库**:使用 Pinecone 或 Milvus。
4. **检索与生成**:构建 Prompt结合检索结果和 LLM。
## 代码实现
\`\`\`python
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=docsearch.as_retriever()
)
\`\`\`
`,
category: 'AI 工程化',
tags: ['LangChain', 'RAG', 'OpenAI'],
isPaid: true
},
{
title: 'SaaS 产品设计与系统架构指南',
summary: '如何设计一个高可用、多租户的 SaaS 系统?本文涵盖数据库设计、权限管理及计费系统实现。',
content: `
## 多租户架构
选择合适的隔离策略:
- **Database per Tenant**:最高隔离性,成本高。
- **Schema per Tenant**:平衡方案。
- **Discriminator Column**:共享表,成本最低,开发复杂度高。
## 权限系统 (RBAC)
设计灵活的角色和权限模型是 SaaS 的核心。
## 计费集成
集成 Stripe 或 Lemon Squeezy 实现订阅支付。
`,
category: '系统设计',
tags: ['SaaS', 'System Design', 'PostgreSQL'],
isPaid: true
},
{
title: '独立开发者如何寻找 Profitable Niche',
summary: '避开红海竞争,发现细分市场的机会。分享 5 个验证产品构想的低成本方法。',
content: `
## 寻找痛点
最好的产品往往源于开发者自己的痛点。
## 验证想法
1. **Landing Page MVP**:先做落地页,收集邮箱。
2. **Pre-sale**:尝试预售。
3. **社区反馈**:在 Reddit 或 Product Hunt 发布。
## 案例分析
分析几个成功的独立开发产品案例...
`,
category: '独立开发',
tags: ['Indie Hacker', 'SaaS'],
isPaid: false
},
{
title: 'React 19 Hook 使用指南',
summary: 'React 19 带来了 use()、useOptimistic() 等新 Hook本文详细介绍它们的用法和场景。',
content: `
## use() Hook
\`use()\` 是一个新的 API用于在组件中读取 Promise 或 Context 的值。
\`\`\`tsx
import { use } from 'react';
function Message({ messagePromise }) {
const message = use(messagePromise);
return <p>{message}</p>;
}
\`\`\`
## useOptimistic
用于处理乐观 UI 更新,提升用户体验。
`,
category: '全栈开发',
tags: ['React', 'Next.js'],
isPaid: false
}
];
const articles = [];
for (let i = 0; i < articlesData.length; i++) {
const data = articlesData[i];
const cat = categories.find(c => c. === data.category) || categories[0];
const articleTags = tags.filter(t => data.tags.includes(t.));
const author = adminUser; // Admin 发布文章
articles.push({
文章标题: data.title,
URL别名: `article-${i + 1}`,
: `https://picsum.photos/seed/${i + 100}/800/450`,
摘要: data.summary,
正文内容: data.content,
SEO关键词: [data.category, ...data.tags],
SEO描述: data.summary,
作者ID: author._id,
分类ID: cat._id,
标签ID列表: articleTags.map(t => t._id),
价格: data.isPaid ? 29.9 : 0,
: 'points',
资源属性: data.isPaid ? {
: 'https://pan.baidu.com/s/demo-link',
: '8888',
: 'aounapp.com'
} : {},
: {
阅读数: getRandomInt(100, 5000),
点赞数: getRandomInt(10, 500),
评论数: 0,
收藏数: getRandomInt(5, 200)
},
: 'published'
});
}
const createdArticles = await Article.insertMany(articles);
// --- 4.6 评论 ---
console.log('💬 创建评论...');
const comments = [];
for (const article of createdArticles) {
const commentCount = getRandomInt(0, 3);
for (let j = 0; j < commentCount; j++) {
const user = getRandomItem(demoUsers);
comments.push({
文章ID: article._id,
用户ID: user._id,
: `这篇文章写得很好,对我帮助很大!期待更多关于 ${article.SEO关键词[0]} 的内容。`,
点赞数: getRandomInt(0, 10),
: 'visible'
});
}
}
await Comment.insertMany(comments);
// 更新文章评论数
for (const article of createdArticles) {
const count = comments.filter(c => c.ID === article._id).length;
await Article.findByIdAndUpdate(article._id, { '统计数据.评论数': count });
}
// --- 4.7 订单 ---
console.log('🧾 创建订单...');
const orders = [];
for (let k = 0; k < 3; k++) {
const user = getRandomItem(demoUsers);
const plan = getRandomItem(plans);
orders.push({
: `ORDER${Date.now()}${k}`,
用户ID: user._id,
: 'buy_membership',
商品ID: plan._id,
: {
标题: plan.套餐名称,
: '/images/membership.png'
},
支付金额: plan.价格,
: 'alipay',
: 'paid',
支付时间: new Date()
});
}
await Order.insertMany(orders);
console.log('✨ 数据库填充完成!');
console.log(`
📊 数据统计:
- 用户: ${allUsers.length} (管理员: admin@aoun.ltd / admin@aoun.ltd)
- 文章: ${createdArticles.length}
- 评论: ${comments.length}
- 订单: ${orders.length}
- 分类: ${categories.length}
- 标签: ${tags.length}
- 会员套餐: ${plans.length}
`);
} catch (error) {
console.error('❌ 数据库填充失败:', error);
process.exit(1);
} finally {
await mongoose.disconnect();
console.log('👋 数据库连接已断开');
process.exit(0);
}
};
seed();