diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1374cf2 --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# 前端环境变量 +# 本地API服务器地址 +VITE_API_BASE_URL=http://localhost:3001/api + +# 后端环境变量 +# 服务器端口 +PORT=3001 + +# JWT密钥(生产环境请使用强密码) +JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_EXPIRES_IN=7d + +# 数据库配置 +DB_PATH=./numerology.db + +# 运行环境 +NODE_ENV=development + +# CORS配置(生产环境请设置具体域名) +CORS_ORIGIN=http://localhost:5173,http://localhost:4173 + +# 日志级别 +LOG_LEVEL=info + +# 会话清理间隔(毫秒) +SESSION_CLEANUP_INTERVAL=3600000 + +# 文件上传限制(MB) +FILE_UPLOAD_LIMIT=10 + +# API请求限制 +RATE_LIMIT_WINDOW_MS=900000 +RATE_LIMIT_MAX_REQUESTS=100 \ No newline at end of file diff --git a/docs/LOCAL_DEPLOYMENT.md b/docs/LOCAL_DEPLOYMENT.md new file mode 100644 index 0000000..007330f --- /dev/null +++ b/docs/LOCAL_DEPLOYMENT.md @@ -0,0 +1,393 @@ +# 本地化部署指南 + +本文档详细说明如何部署和运行完全本地化的三算命应用。 + +## 🎯 本地化改造概述 + +本项目已从基于Supabase的云端架构完全转换为本地化架构: + +### 架构变更 +- **数据库**: PostgreSQL (Supabase) → SQLite (本地文件) +- **后端**: Supabase Edge Functions → Express.js 服务器 +- **认证**: Supabase Auth → JWT + bcrypt +- **API**: Supabase客户端 → 本地API客户端 + +### 保留功能 +- ✅ 完整的八字、紫微、易经分析功能 +- ✅ 用户注册、登录、档案管理 +- ✅ 历史记录存储和查询 +- ✅ 所有业务逻辑和算法 +- ✅ 原有的用户界面和体验 + +## 📋 环境要求 + +### 系统要求 +- Node.js >= 18.0.0 +- npm >= 9.0.0 或 pnpm >= 8.0.0 +- Git >= 2.0.0 + +### 检查环境 +```bash +node --version # 应该 >= 18.0.0 +npm --version # 应该 >= 9.0.0 +git --version # 应该 >= 2.0.0 +``` + +## 🚀 快速开始 + +### 1. 克隆项目 +```bash +git clone +cd ai-numerology-refactored +``` + +### 2. 安装依赖 +```bash +npm install +``` + +### 3. 环境配置 +```bash +# 复制环境变量模板 +cp .env.example .env + +# 编辑环境变量(可选) +# 默认配置已经可以直接使用 +``` + +### 4. 初始化数据库 +```bash +npm run db:init +``` + +执行成功后会看到: +``` +🎉 数据库初始化完成! +📍 数据库文件位置: ./numerology.db +✅ 管理员用户创建成功 + 邮箱: admin@localhost + 密码: admin123 +✅ 示例数据创建成功 + 测试用户邮箱: test@example.com + 测试用户密码: test123 +``` + +### 5. 启动应用 + +#### 开发模式(推荐) +```bash +npm run dev +``` +这会同时启动后端服务器和前端开发服务器。 + +#### 分别启动 +```bash +# 终端1:启动后端服务器 +npm run server + +# 终端2:启动前端开发服务器 +npx vite +``` + +### 6. 访问应用 +- 前端地址: http://localhost:5173 +- 后端API: http://localhost:3001 +- 健康检查: http://localhost:3001/health + +## 🔧 配置说明 + +### 环境变量 + +#### 前端环境变量 +```env +# 本地API服务器地址 +VITE_API_BASE_URL=http://localhost:3001/api +``` + +#### 后端环境变量 +```env +# 服务器端口 +PORT=3001 + +# JWT密钥(生产环境请更改) +JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_EXPIRES_IN=7d + +# 数据库文件路径 +DB_PATH=./numerology.db + +# 运行环境 +NODE_ENV=development +``` + +### 数据库配置 + +数据库文件默认位置:`./numerology.db` + +#### 数据库管理命令 +```bash +# 初始化数据库 +npm run db:init + +# 备份数据库 +node server/scripts/initDatabase.cjs backup + +# 清理过期数据 +node server/scripts/initDatabase.cjs cleanup +``` + +## 🏗️ 项目结构 + +``` +ai-numerology-refactored/ +├── server/ # 后端服务器 +│ ├── database/ # 数据库相关 +│ │ ├── index.cjs # 数据库管理器 +│ │ └── schema.sql # 数据库结构 +│ ├── middleware/ # 中间件 +│ │ ├── auth.cjs # JWT认证 +│ │ ├── errorHandler.cjs # 错误处理 +│ │ └── logger.cjs # 日志记录 +│ ├── routes/ # API路由 +│ │ ├── auth.cjs # 认证路由 +│ │ ├── analysis.cjs # 分析路由 +│ │ ├── history.cjs # 历史记录路由 +│ │ └── profile.cjs # 用户档案路由 +│ ├── services/ # 业务逻辑服务 +│ │ ├── baziAnalyzer.cjs # 八字分析 +│ │ ├── yijingAnalyzer.cjs # 易经分析 +│ │ └── ziweiAnalyzer.cjs # 紫微分析 +│ ├── scripts/ # 工具脚本 +│ │ └── initDatabase.cjs # 数据库初始化 +│ └── index.cjs # 服务器入口 +├── src/ # 前端源码 +│ ├── lib/ +│ │ └── localApi.ts # 本地API客户端 +│ ├── contexts/ +│ │ └── AuthContext.tsx # 认证上下文 +│ └── ... +├── logic/ # 原始推理逻辑(参考) +├── numerology.db # SQLite数据库文件 +├── .env.example # 环境变量模板 +└── package.json # 项目配置 +``` + +## 🔐 用户账户 + +### 预设账户 + +#### 管理员账户 +- 邮箱: `admin@localhost` +- 密码: `admin123` +- 权限: 完整访问权限 + +#### 测试账户 +- 邮箱: `test@example.com` +- 密码: `test123` +- 权限: 普通用户权限 +- 包含示例分析记录 + +### 创建新用户 +1. 访问注册页面 +2. 填写邮箱和密码 +3. 可选填写姓名 +4. 点击注册 + +## 📊 API接口 + +### 认证接口 +- `POST /api/auth/register` - 用户注册 +- `POST /api/auth/login` - 用户登录 +- `POST /api/auth/logout` - 用户登出 +- `GET /api/auth/me` - 获取当前用户信息 + +### 分析接口 +- `POST /api/analysis/bazi` - 八字分析 +- `POST /api/analysis/ziwei` - 紫微斗数分析 +- `POST /api/analysis/yijing` - 易经占卜分析 +- `GET /api/analysis/types` - 获取分析类型 + +### 历史记录接口 +- `GET /api/history` - 获取历史记录 +- `GET /api/history/:id` - 获取单个记录 +- `DELETE /api/history/:id` - 删除记录 + +### 用户档案接口 +- `GET /api/profile` - 获取用户档案 +- `PUT /api/profile` - 更新用户档案 + +## 🛠️ 开发指南 + +### 开发模式启动 +```bash +# 同时启动前后端(推荐) +npm run dev + +# 或分别启动 +npm run server # 后端 +npx vite # 前端 +``` + +### 代码热重载 +- 后端:使用 nodemon 自动重启 +- 前端:使用 Vite 热模块替换 + +### 调试 +- 后端日志:控制台输出 +- 前端调试:浏览器开发者工具 +- API测试:可使用 Postman 或 curl + +## 🚢 生产部署 + +### 1. 构建前端 +```bash +npm run build +``` + +### 2. 启动生产服务器 +```bash +# 设置生产环境 +export NODE_ENV=production + +# 启动服务器 +npm start +``` + +### 3. 使用 PM2 管理进程(推荐) +```bash +# 安装 PM2 +npm install -g pm2 + +# 启动应用 +pm2 start server/index.cjs --name "numerology-app" + +# 查看状态 +pm2 status + +# 查看日志 +pm2 logs numerology-app +``` + +### 4. 反向代理配置(Nginx) +```nginx +server { + listen 80; + server_name your-domain.com; + + # 前端静态文件 + location / { + root /path/to/dist; + try_files $uri $uri/ /index.html; + } + + # API代理 + location /api { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## 🔧 故障排除 + +### 常见问题 + +#### 1. 数据库初始化失败 +```bash +# 删除现有数据库文件 +rm numerology.db + +# 重新初始化 +npm run db:init +``` + +#### 2. 端口被占用 +```bash +# 查看端口占用 +netstat -ano | findstr :3001 + +# 修改端口(在 .env 文件中) +PORT=3002 +``` + +#### 3. 前端无法连接后端 +- 检查后端服务器是否启动 +- 检查 `VITE_API_BASE_URL` 配置 +- 检查防火墙设置 + +#### 4. JWT token 过期 +```bash +# 清除浏览器 localStorage +# 或重新登录 +``` + +### 日志查看 +```bash +# 后端日志 +npm run server + +# 如果使用 PM2 +pm2 logs numerology-app +``` + +## 📈 性能优化 + +### 数据库优化 +- 定期清理过期会话:`node server/scripts/initDatabase.cjs cleanup` +- 数据库备份:`node server/scripts/initDatabase.cjs backup` + +### 前端优化 +- 构建优化:`npm run build` +- 启用 gzip 压缩 +- 使用 CDN 加速静态资源 + +## 🔒 安全建议 + +### 生产环境安全 +1. **更改默认密码** + ```env + JWT_SECRET=your-very-secure-random-string + ``` + +2. **启用 HTTPS** + - 使用 SSL 证书 + - 配置安全头 + +3. **数据库安全** + - 定期备份数据库 + - 限制数据库文件访问权限 + +4. **API安全** + - 实施请求频率限制 + - 输入验证和清理 + - 错误信息不暴露敏感信息 + +## 📞 技术支持 + +### 获取帮助 +- 查看项目文档 +- 检查 GitHub Issues +- 查看错误日志 + +### 报告问题 +请提供以下信息: +- 操作系统版本 +- Node.js 版本 +- 错误日志 +- 复现步骤 + +--- + +## 🎉 恭喜! + +您已成功部署本地化的三算命应用!现在可以: +- 🔮 进行八字、紫微、易经分析 +- 👤 管理用户账户和档案 +- 📚 查看和管理历史记录 +- 🔒 享受完全本地化的数据隐私保护 + +应用完全运行在本地环境,无需依赖任何外部服务,数据安全可控。 \ No newline at end of file diff --git a/numerology.db b/numerology.db index 7cb7e60..55849fa 100644 Binary files a/numerology.db and b/numerology.db differ diff --git a/numerology.db-shm b/numerology.db-shm new file mode 100644 index 0000000..38ae25f Binary files /dev/null and b/numerology.db-shm differ diff --git a/numerology.db-wal b/numerology.db-wal new file mode 100644 index 0000000..49a7f80 Binary files /dev/null and b/numerology.db-wal differ diff --git a/package-lock.json b/package-lock.json index 43b94ed..ed24f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,21 +36,23 @@ "@radix-ui/react-toggle": "^1.1.1", "@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", - "@supabase/supabase-js": "^2.55.0", - "bcryptjs": "^3.0.2", + "bcryptjs": "^2.4.3", "better-sqlite3": "^12.2.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", + "concurrently": "^8.2.2", "cors": "^2.8.5", "date-fns": "^3.0.0", "embla-carousel-react": "^8.5.2", - "express": "^5.1.0", - "helmet": "^8.1.0", + "express": "^4.18.2", + "helmet": "^7.1.0", "input-otp": "^1.4.2", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.364.0", "next-themes": "^0.4.4", + "node-fetch": "^2.7.0", + "nodemon": "^3.0.2", "react": "^18.3.1", "react-day-picker": "8.10.1", "react-dom": "^18.3.1", @@ -66,13 +68,16 @@ }, "devDependencies": { "@eslint/js": "^9.15.0", + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@types/node": "^22.10.7", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@types/react-router-dom": "^5", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "10.4.20", - "concurrently": "^9.2.0", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.14", @@ -2858,80 +2863,6 @@ "win32" ] }, - "node_modules/@supabase/auth-js": { - "version": "2.71.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz", - "integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "^2.6.14" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz", - "integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "^2.6.14" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", - "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "^2.6.14" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.1.tgz", - "integrity": "sha512-edRFa2IrQw50kNntvUyS38hsL7t2d/psah6om6aNTLLcWem0R6bOUq7sk7DsGeSlNfuwEwWn57FdYSva6VddYw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "^2.6.13", - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "ws": "^8.18.2" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.11.0.tgz", - "integrity": "sha512-Y+kx/wDgd4oasAgoAq0bsbQojwQ+ejIif8uczZ9qufRHWFLMU5cODT+ApHsSrDufqUcVKt+eyxtOXSkeh2v9ww==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "^2.6.14" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.55.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.55.0.tgz", - "integrity": "sha512-Y1uV4nEMjQV1x83DGn7+Z9LOisVVRlY1geSARrUHbXWgbyKLZ6/08dvc0Us1r6AJ4tcKpwpCZWG9yDQYo1JgHg==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.71.1", - "@supabase/functions-js": "2.4.5", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "1.19.4", - "@supabase/realtime-js": "2.15.1", - "@supabase/storage-js": "^2.10.4" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2977,6 +2908,44 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -3047,6 +3016,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/history": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", @@ -3054,6 +3049,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3061,21 +3063,41 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.17.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -3083,6 +3105,20 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "18.3.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", @@ -3127,15 +3163,29 @@ "@types/react-router": "*" } }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, "license": "MIT", "dependencies": { + "@types/mime": "^1", "@types/node": "*" } }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.39.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", @@ -3461,13 +3511,13 @@ "license": "MIT" }, "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -3584,6 +3634,12 @@ "node": ">=10" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -3649,13 +3705,10 @@ "license": "MIT" }, "node_modules/bcryptjs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", - "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", - "license": "BSD-3-Clause", - "bin": { - "bcrypt": "bin/bcrypt" - } + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" }, "node_modules/better-sqlite3": { "version": "12.2.0", @@ -3704,30 +3757,48 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3891,7 +3962,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3962,7 +4032,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -3977,7 +4046,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3987,14 +4055,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4009,7 +4075,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4022,7 +4087,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -4508,20 +4572,20 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", - "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", - "dev": true, + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", + "date-fns": "^2.30.0", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" @@ -4531,17 +4595,32 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=18" + "node": "^14.13.0 || >=16.0.0" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/concurrently/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4554,9 +4633,9 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -4582,22 +4661,19 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", @@ -4838,6 +4914,16 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -5058,7 +5144,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5293,47 +5378,66 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 18" + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5433,22 +5537,38 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5527,12 +5647,12 @@ } }, "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/fs-constants": { @@ -5578,7 +5698,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5728,7 +5847,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5759,12 +5877,12 @@ } }, "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", + "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, "node_modules/http-errors": { @@ -5783,22 +5901,13 @@ "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" @@ -5834,6 +5943,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5967,12 +6082,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6288,22 +6397,19 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "engines": { - "node": ">=18" - }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -6317,6 +6423,15 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -6330,22 +6445,34 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -6367,7 +6494,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6449,9 +6575,9 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6491,6 +6617,26 @@ "node": ">=10" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -6498,6 +6644,67 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6694,13 +6901,10 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -6947,6 +7151,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -6968,12 +7178,12 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -7012,14 +7222,14 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "engines": { @@ -7334,7 +7544,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7420,22 +7629,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7463,7 +7656,6 @@ "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -7515,40 +7707,66 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">= 18" + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">= 18" + "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { @@ -7582,7 +7800,6 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7720,6 +7937,30 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -7739,10 +7980,15 @@ "node": ">=0.10.0" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==" + }, "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7892,7 +8138,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8093,6 +8338,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8103,7 +8357,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, "license": "MIT", "bin": { "tree-kill": "cli.js" @@ -8160,14 +8413,13 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" @@ -8211,10 +8463,17 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -8325,6 +8584,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8657,32 +8925,10 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -8711,7 +8957,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -8730,7 +8975,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -8740,7 +8984,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8750,14 +8993,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8772,7 +9013,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" diff --git a/package.json b/package.json index eb0c150..133a92b 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "yes | pnpm install && vite", - "build": "yes | pnpm install && rm -rf node_modules/.vite-temp && tsc -b && vite build", - "build:prod": "yes | pnpm install && rm -rf node_modules/.vite-temp && tsc -b && BUILD_MODE=prod vite build", - "lint": "yes | pnpm install && eslint .", - "preview": "yes | pnpm install && vite preview" + "dev": "concurrently \"npm run server\" \"vite\"", + "server": "nodemon server/index.cjs", + "build": "tsc -b && vite build", + "build:prod": "tsc -b && BUILD_MODE=prod vite build", + "lint": "eslint .", + "preview": "vite preview", + "start": "node server/index.cjs", + "db:init": "node server/scripts/initDatabase.cjs" }, "dependencies": { "@hookform/resolvers": "^3.10.0", @@ -39,21 +42,23 @@ "@radix-ui/react-toggle": "^1.1.1", "@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", - "@supabase/supabase-js": "^2.55.0", - "bcryptjs": "^3.0.2", + "bcryptjs": "^2.4.3", "better-sqlite3": "^12.2.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", + "concurrently": "^8.2.2", "cors": "^2.8.5", "date-fns": "^3.0.0", "embla-carousel-react": "^8.5.2", - "express": "^5.1.0", - "helmet": "^8.1.0", + "express": "^4.18.2", + "helmet": "^7.1.0", "input-otp": "^1.4.2", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.364.0", "next-themes": "^0.4.4", + "node-fetch": "^2.7.0", + "nodemon": "^3.0.2", "react": "^18.3.1", "react-day-picker": "8.10.1", "react-dom": "^18.3.1", @@ -69,6 +74,10 @@ }, "devDependencies": { "@eslint/js": "^9.15.0", + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@types/node": "^22.10.7", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", diff --git a/server/database/index.cjs b/server/database/index.cjs new file mode 100644 index 0000000..8a080e6 --- /dev/null +++ b/server/database/index.cjs @@ -0,0 +1,127 @@ +const Database = require('better-sqlite3'); +const path = require('path'); +const fs = require('fs'); + +class DatabaseManager { + constructor() { + this.db = null; + this.dbPath = path.join(__dirname, '../../numerology.db'); + this.schemaPath = path.join(__dirname, 'schema.sql'); + } + + // 初始化数据库连接 + init() { + try { + // 创建或连接到SQLite数据库 + this.db = new Database(this.dbPath); + + // 启用外键约束 + this.db.pragma('foreign_keys = ON'); + + // 设置WAL模式以提高并发性能 + this.db.pragma('journal_mode = WAL'); + + // 初始化数据库结构 + this.initializeSchema(); + + console.log('数据库初始化成功'); + return this.db; + } catch (error) { + console.error('数据库初始化失败:', error); + throw error; + } + } + + // 初始化数据库结构 + initializeSchema() { + try { + const schema = fs.readFileSync(this.schemaPath, 'utf8'); + + // 直接执行整个schema文件 + this.db.exec(schema); + + console.log('数据库结构初始化完成'); + } catch (error) { + console.error('数据库结构初始化失败:', error); + throw error; + } + } + + // 获取数据库实例 + getDatabase() { + if (!this.db) { + this.init(); + } + return this.db; + } + + // 关闭数据库连接 + close() { + if (this.db) { + this.db.close(); + this.db = null; + console.log('数据库连接已关闭'); + } + } + + // 执行事务 + transaction(callback) { + const db = this.getDatabase(); + const transaction = db.transaction(callback); + return transaction; + } + + // 备份数据库 + backup(backupPath) { + try { + const db = this.getDatabase(); + db.backup(backupPath); + console.log(`数据库备份成功: ${backupPath}`); + } catch (error) { + console.error('数据库备份失败:', error); + throw error; + } + } + + // 清理过期会话 + cleanupExpiredSessions() { + try { + const db = this.getDatabase(); + const stmt = db.prepare('DELETE FROM user_sessions WHERE expires_at < ?'); + const result = stmt.run(new Date().toISOString()); + console.log(`清理了 ${result.changes} 个过期会话`); + return result.changes; + } catch (error) { + console.error('清理过期会话失败:', error); + throw error; + } + } +} + +// 创建单例实例 +const dbManager = new DatabaseManager(); + +// 导出数据库管理器和便捷方法 +module.exports = { + dbManager, + getDB: () => dbManager.getDatabase(), + closeDB: () => dbManager.close(), + transaction: (callback) => dbManager.transaction(callback), + backup: (path) => dbManager.backup(path), + cleanupSessions: () => dbManager.cleanupExpiredSessions() +}; + +// 进程退出时自动关闭数据库 +process.on('exit', () => { + dbManager.close(); +}); + +process.on('SIGINT', () => { + dbManager.close(); + process.exit(0); +}); + +process.on('SIGTERM', () => { + dbManager.close(); + process.exit(0); +}); \ No newline at end of file diff --git a/server/database/index.js b/server/database/index.js new file mode 100644 index 0000000..e69de29 diff --git a/server/database/schema.sql b/server/database/schema.sql new file mode 100644 index 0000000..b65b003 --- /dev/null +++ b/server/database/schema.sql @@ -0,0 +1,96 @@ +-- 三算命本地化数据库Schema +-- SQLite数据库结构定义 + +-- 用户表 +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 用户档案表 +CREATE TABLE IF NOT EXISTS user_profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + username TEXT, + full_name TEXT, + birth_date TEXT, + birth_time TEXT, + birth_location TEXT, + gender TEXT CHECK (gender IN ('male', 'female')), + avatar_url TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- 命理分析记录表 (兼容现有numerology_readings表结构) +CREATE TABLE IF NOT EXISTS numerology_readings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + reading_type TEXT NOT NULL CHECK (reading_type IN ('bazi', 'ziwei', 'yijing', 'wuxing')), + name TEXT, + birth_date TEXT, + birth_time TEXT, + birth_place TEXT, + gender TEXT, + input_data TEXT, -- JSON格式存储输入数据 + results TEXT, -- JSON格式存储分析结果(向后兼容) + analysis TEXT, -- JSON格式存储新格式分析结果 + status TEXT DEFAULT 'completed' CHECK (status IN ('pending', 'processing', 'completed', 'failed')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- 会话表 (用于JWT token管理) +CREATE TABLE IF NOT EXISTS user_sessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + token_hash TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- 创建索引以提高查询性能 +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); +CREATE INDEX IF NOT EXISTS idx_user_profiles_user_id ON user_profiles(user_id); +CREATE INDEX IF NOT EXISTS idx_readings_user_id ON numerology_readings(user_id); +CREATE INDEX IF NOT EXISTS idx_readings_type ON numerology_readings(reading_type); +CREATE INDEX IF NOT EXISTS idx_readings_created_at ON numerology_readings(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON user_sessions(user_id); +CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(token_hash); +CREATE INDEX IF NOT EXISTS idx_sessions_expires ON user_sessions(expires_at); + +-- 触发器:自动更新updated_at字段 +CREATE TRIGGER IF NOT EXISTS update_users_timestamp + AFTER UPDATE ON users + FOR EACH ROW + BEGIN + UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + END; + +CREATE TRIGGER IF NOT EXISTS update_user_profiles_timestamp + AFTER UPDATE ON user_profiles + FOR EACH ROW + BEGIN + UPDATE user_profiles SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + END; + +CREATE TRIGGER IF NOT EXISTS update_numerology_readings_timestamp + AFTER UPDATE ON numerology_readings + FOR EACH ROW + BEGIN + UPDATE numerology_readings SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + END; + +-- 清理过期会话的触发器 +CREATE TRIGGER IF NOT EXISTS cleanup_expired_sessions + AFTER INSERT ON user_sessions + FOR EACH ROW + BEGIN + DELETE FROM user_sessions WHERE expires_at < datetime('now'); + END; \ No newline at end of file diff --git a/server/index.cjs b/server/index.cjs new file mode 100644 index 0000000..c6c3d56 --- /dev/null +++ b/server/index.cjs @@ -0,0 +1,133 @@ +const express = require('express'); +const cors = require('cors'); +const helmet = require('helmet'); +const path = require('path'); +const { dbManager } = require('./database/index.cjs'); + +// 导入路由 +const authRoutes = require('./routes/auth.cjs'); +const analysisRoutes = require('./routes/analysis.cjs'); +const historyRoutes = require('./routes/history.cjs'); +const profileRoutes = require('./routes/profile.cjs'); + +// 导入中间件 +const { errorHandler } = require('./middleware/errorHandler.cjs'); +const { requestLogger } = require('./middleware/logger.cjs'); + +const app = express(); +const PORT = process.env.PORT || 3001; + +// 初始化数据库 +try { + dbManager.init(); + console.log('数据库连接成功'); +} catch (error) { + console.error('数据库连接失败:', error); + process.exit(1); +} + +// 安全中间件 +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + }, + }, + crossOriginEmbedderPolicy: false +})); + +// CORS配置 +app.use(cors({ + origin: process.env.NODE_ENV === 'production' + ? ['http://localhost:5173', 'http://localhost:4173'] // 生产环境允许的域名 + : true, // 开发环境允许所有域名 + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] +})); + +// 基础中间件 +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); +app.use(requestLogger); + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + database: 'connected' + }); +}); + +// API路由 +app.use('/api/auth', authRoutes); +app.use('/api/analysis', analysisRoutes); +app.use('/api/history', historyRoutes); +app.use('/api/profile', profileRoutes); + +// 静态文件服务 (用于生产环境) +if (process.env.NODE_ENV === 'production') { + app.use(express.static(path.join(__dirname, '../dist'))); + + // SPA路由处理 + app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '../dist/index.html')); + }); +} + +// 404处理 +app.use('*', (req, res) => { + res.status(404).json({ + error: { + code: 'NOT_FOUND', + message: '请求的资源不存在' + } + }); +}); + +// 错误处理中间件 +app.use(errorHandler); + +// 启动服务器 +const server = app.listen(PORT, () => { + console.log(`🚀 服务器运行在 http://localhost:${PORT}`); + console.log(`📊 数据库文件: ${path.resolve('./numerology.db')}`); + console.log(`🌍 环境: ${process.env.NODE_ENV || 'development'}`); +}); + +// 优雅关闭 +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,开始优雅关闭...'); + server.close(() => { + console.log('HTTP服务器已关闭'); + dbManager.close(); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + console.log('收到SIGINT信号,开始优雅关闭...'); + server.close(() => { + console.log('HTTP服务器已关闭'); + dbManager.close(); + process.exit(0); + }); +}); + +// 未捕获异常处理 +process.on('uncaughtException', (error) => { + console.error('未捕获的异常:', error); + dbManager.close(); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); + console.error('Promise:', promise); +}); + +module.exports = app; \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..e69de29 diff --git a/server/middleware/auth.cjs b/server/middleware/auth.cjs new file mode 100644 index 0000000..e23016a --- /dev/null +++ b/server/middleware/auth.cjs @@ -0,0 +1,162 @@ +const jwt = require('jsonwebtoken'); +const { getDB } = require('../database/index.cjs'); +const { AppError } = require('./errorHandler.cjs'); + +// JWT密钥 (在生产环境中应该从环境变量读取) +const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production'; +const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d'; + +// 生成JWT token +const generateToken = (userId) => { + return jwt.sign({ userId }, JWT_SECRET, { + expiresIn: JWT_EXPIRES_IN + }); +}; + +// 验证JWT token +const verifyToken = (token) => { + try { + return jwt.verify(token, JWT_SECRET); + } catch (error) { + throw new AppError('无效的访问令牌', 401, 'INVALID_TOKEN'); + } +}; + +// 认证中间件 +const authenticate = async (req, res, next) => { + try { + // 从请求头获取token + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + throw new AppError('缺少访问令牌', 401, 'MISSING_TOKEN'); + } + + const token = authHeader.substring(7); // 移除 'Bearer ' 前缀 + + // 验证token + const decoded = verifyToken(token); + + // 从数据库获取用户信息 + const db = getDB(); + const user = db.prepare('SELECT id, email FROM users WHERE id = ?').get(decoded.userId); + + if (!user) { + throw new AppError('用户不存在', 401, 'USER_NOT_FOUND'); + } + + // 检查会话是否有效 + const session = db.prepare( + 'SELECT id FROM user_sessions WHERE user_id = ? AND token_hash = ? AND expires_at > ?' + ).get(user.id, hashToken(token), new Date().toISOString()); + + if (!session) { + throw new AppError('会话已过期,请重新登录', 401, 'SESSION_EXPIRED'); + } + + // 将用户信息添加到请求对象 + req.user = user; + req.token = token; + + next(); + } catch (error) { + next(error); + } +}; + +// 可选认证中间件(不强制要求登录) +const optionalAuth = async (req, res, next) => { + try { + const authHeader = req.headers.authorization; + + if (authHeader && authHeader.startsWith('Bearer ')) { + const token = authHeader.substring(7); + const decoded = verifyToken(token); + + const db = getDB(); + const user = db.prepare('SELECT id, email FROM users WHERE id = ?').get(decoded.userId); + + if (user) { + const session = db.prepare( + 'SELECT id FROM user_sessions WHERE user_id = ? AND token_hash = ? AND expires_at > ?' + ).get(user.id, hashToken(token), new Date().toISOString()); + + if (session) { + req.user = user; + req.token = token; + } + } + } + + next(); + } catch (error) { + // 可选认证失败时不抛出错误,继续执行 + next(); + } +}; + +// 创建用户会话 +const createSession = (userId, token) => { + const db = getDB(); + const expiresAt = new Date(); + expiresAt.setDate(expiresAt.getDate() + 7); // 7天后过期 + + const stmt = db.prepare( + 'INSERT INTO user_sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)' + ); + + return stmt.run(userId, hashToken(token), expiresAt.toISOString()); +}; + +// 删除用户会话 +const deleteSession = (userId, token) => { + const db = getDB(); + const stmt = db.prepare( + 'DELETE FROM user_sessions WHERE user_id = ? AND token_hash = ?' + ); + + return stmt.run(userId, hashToken(token)); +}; + +// 删除用户所有会话 +const deleteAllSessions = (userId) => { + const db = getDB(); + const stmt = db.prepare('DELETE FROM user_sessions WHERE user_id = ?'); + + return stmt.run(userId); +}; + +// Token哈希函数(简单实现) +const hashToken = (token) => { + const crypto = require('crypto'); + return crypto.createHash('sha256').update(token).digest('hex'); +}; + +// 清理过期会话 +const cleanupExpiredSessions = () => { + const db = getDB(); + const stmt = db.prepare('DELETE FROM user_sessions WHERE expires_at < ?'); + const result = stmt.run(new Date().toISOString()); + + if (result.changes > 0) { + console.log(`清理了 ${result.changes} 个过期会话`); + } + + return result.changes; +}; + +// 定期清理过期会话(每小时执行一次) +setInterval(cleanupExpiredSessions, 60 * 60 * 1000); + +module.exports = { + generateToken, + verifyToken, + authenticate, + optionalAuth, + createSession, + deleteSession, + deleteAllSessions, + cleanupExpiredSessions, + JWT_SECRET, + JWT_EXPIRES_IN +}; \ No newline at end of file diff --git a/server/middleware/auth.js b/server/middleware/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/server/middleware/errorHandler.cjs b/server/middleware/errorHandler.cjs new file mode 100644 index 0000000..f9e48f6 --- /dev/null +++ b/server/middleware/errorHandler.cjs @@ -0,0 +1,93 @@ +// 错误处理中间件 + +class AppError extends Error { + constructor(message, statusCode, code = null) { + super(message); + this.statusCode = statusCode; + this.code = code; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} + +// 错误处理中间件 +const errorHandler = (err, req, res, next) => { + let error = { ...err }; + error.message = err.message; + + // 记录错误日志 + console.error('错误详情:', { + message: err.message, + stack: err.stack, + url: req.url, + method: req.method, + ip: req.ip, + userAgent: req.get('User-Agent'), + timestamp: new Date().toISOString() + }); + + // SQLite错误处理 + if (err.code === 'SQLITE_CONSTRAINT_UNIQUE') { + const message = '数据已存在,请检查输入'; + error = new AppError(message, 400, 'DUPLICATE_ENTRY'); + } + + if (err.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') { + const message = '关联数据不存在'; + error = new AppError(message, 400, 'FOREIGN_KEY_CONSTRAINT'); + } + + // JWT错误处理 + if (err.name === 'JsonWebTokenError') { + const message = '无效的访问令牌'; + error = new AppError(message, 401, 'INVALID_TOKEN'); + } + + if (err.name === 'TokenExpiredError') { + const message = '访问令牌已过期'; + error = new AppError(message, 401, 'TOKEN_EXPIRED'); + } + + // 验证错误处理 + if (err.name === 'ValidationError') { + const message = '输入数据验证失败'; + error = new AppError(message, 400, 'VALIDATION_ERROR'); + } + + // 默认错误响应 + const statusCode = error.statusCode || 500; + const errorCode = error.code || 'INTERNAL_ERROR'; + const message = error.isOperational ? error.message : '服务器内部错误'; + + res.status(statusCode).json({ + error: { + code: errorCode, + message: message, + ...(process.env.NODE_ENV === 'development' && { + stack: err.stack, + details: err + }) + } + }); +}; + +// 异步错误捕获包装器 +const asyncHandler = (fn) => { + return (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +}; + +// 404错误处理 +const notFound = (req, res, next) => { + const error = new AppError(`请求的资源 ${req.originalUrl} 不存在`, 404, 'NOT_FOUND'); + next(error); +}; + +module.exports = { + AppError, + errorHandler, + asyncHandler, + notFound +}; \ No newline at end of file diff --git a/server/middleware/errorHandler.js b/server/middleware/errorHandler.js new file mode 100644 index 0000000..e69de29 diff --git a/server/middleware/logger.cjs b/server/middleware/logger.cjs new file mode 100644 index 0000000..1b4c457 --- /dev/null +++ b/server/middleware/logger.cjs @@ -0,0 +1,95 @@ +// 请求日志记录中间件 + +const requestLogger = (req, res, next) => { + const start = Date.now(); + const timestamp = new Date().toISOString(); + + // 记录请求开始 + console.log(`[${timestamp}] ${req.method} ${req.url} - ${req.ip}`); + + // 监听响应结束事件 + res.on('finish', () => { + const duration = Date.now() - start; + const statusColor = getStatusColor(res.statusCode); + + console.log( + `[${new Date().toISOString()}] ${req.method} ${req.url} - ` + + `${statusColor}${res.statusCode}\x1b[0m - ${duration}ms - ${req.ip}` + ); + + // 记录慢请求 + if (duration > 1000) { + console.warn(`⚠️ 慢请求警告: ${req.method} ${req.url} - ${duration}ms`); + } + }); + + next(); +}; + +// 获取状态码颜色 +function getStatusColor(statusCode) { + if (statusCode >= 500) return '\x1b[31m'; // 红色 + if (statusCode >= 400) return '\x1b[33m'; // 黄色 + if (statusCode >= 300) return '\x1b[36m'; // 青色 + if (statusCode >= 200) return '\x1b[32m'; // 绿色 + return '\x1b[0m'; // 默认 +} + +// API访问日志记录 +const apiLogger = (req, res, next) => { + // 记录API调用详情 + const logData = { + timestamp: new Date().toISOString(), + method: req.method, + url: req.url, + ip: req.ip, + userAgent: req.get('User-Agent'), + contentType: req.get('Content-Type'), + contentLength: req.get('Content-Length'), + userId: req.user?.id || null + }; + + // 在开发环境下记录请求体(排除敏感信息) + if (process.env.NODE_ENV === 'development' && req.body) { + const sanitizedBody = { ...req.body }; + // 移除敏感字段 + delete sanitizedBody.password; + delete sanitizedBody.password_hash; + delete sanitizedBody.token; + + if (Object.keys(sanitizedBody).length > 0) { + logData.body = sanitizedBody; + } + } + + console.log('API调用:', JSON.stringify(logData, null, 2)); + next(); +}; + +// 错误日志记录 +const errorLogger = (error, req, res, next) => { + const errorLog = { + timestamp: new Date().toISOString(), + error: { + message: error.message, + stack: error.stack, + code: error.code + }, + request: { + method: req.method, + url: req.url, + ip: req.ip, + userAgent: req.get('User-Agent'), + userId: req.user?.id || null + } + }; + + console.error('错误日志:', JSON.stringify(errorLog, null, 2)); + next(error); +}; + +module.exports = { + requestLogger, + apiLogger, + errorLogger +}; \ No newline at end of file diff --git a/server/middleware/logger.js b/server/middleware/logger.js new file mode 100644 index 0000000..e69de29 diff --git a/server/routes/analysis.cjs b/server/routes/analysis.cjs new file mode 100644 index 0000000..4233f11 --- /dev/null +++ b/server/routes/analysis.cjs @@ -0,0 +1,425 @@ +const express = require('express'); +const { getDB } = require('../database/index.cjs'); +const { authenticate } = require('../middleware/auth.cjs'); +const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs'); + +// 导入分析服务 +const BaziAnalyzer = require('../services/baziAnalyzer.cjs'); +const YijingAnalyzer = require('../services/yijingAnalyzer.cjs'); +const ZiweiAnalyzer = require('../services/ziweiAnalyzer.cjs'); + +const router = express.Router(); + +// 初始化分析器 +const baziAnalyzer = new BaziAnalyzer(); +const yijingAnalyzer = new YijingAnalyzer(); +const ziweiAnalyzer = new ZiweiAnalyzer(); + +// 八字分析接口 +router.post('/bazi', authenticate, asyncHandler(async (req, res) => { + const { birth_data } = req.body; + + // 输入验证 + if (!birth_data || !birth_data.name || !birth_data.birth_date) { + throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA'); + } + + // 验证出生日期格式 + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birth_data.birth_date)) { + throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT'); + } + + // 验证出生时间格式(如果提供) + if (birth_data.birth_time) { + const timeRegex = /^\d{2}:\d{2}$/; + if (!timeRegex.test(birth_data.birth_time)) { + throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT'); + } + } + + try { + // 执行八字分析 + const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birth_data); + + // 保存到数据库 + const db = getDB(); + const insertReading = db.prepare(` + INSERT INTO numerology_readings ( + user_id, reading_type, name, birth_date, birth_time, birth_place, gender, + input_data, analysis, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = insertReading.run( + req.user.id, + 'bazi', + birth_data.name, + birth_data.birth_date, + birth_data.birth_time || null, + birth_data.birth_place || null, + birth_data.gender || null, + JSON.stringify(birth_data), + JSON.stringify(analysisResult), + 'completed' + ); + + res.json({ + data: { + record_id: result.lastInsertRowid, + analysis: analysisResult + } + }); + } catch (error) { + console.error('八字分析错误:', error); + throw new AppError('八字分析过程中发生错误', 500, 'BAZI_ANALYSIS_ERROR'); + } +})); + +// 易经分析接口 +router.post('/yijing', authenticate, asyncHandler(async (req, res) => { + const { birth_data, question } = req.body; + + // 输入验证 + if (!birth_data || !birth_data.name) { + throw new AppError('缺少必要参数:姓名', 400, 'MISSING_BIRTH_DATA'); + } + + const finalQuestion = question || '人生运势综合占卜'; + + try { + // 执行易经分析 + const analysisResult = yijingAnalyzer.performYijingAnalysis({ + question: finalQuestion, + user_id: req.user.id, + birth_data: birth_data + }); + + // 保存到数据库 + const db = getDB(); + const insertReading = db.prepare(` + INSERT INTO numerology_readings ( + user_id, reading_type, name, birth_date, birth_time, birth_place, gender, + input_data, analysis, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = insertReading.run( + req.user.id, + 'yijing', + birth_data.name, + birth_data.birth_date || null, + birth_data.birth_time || null, + birth_data.birth_place || null, + birth_data.gender || null, + JSON.stringify({ question: finalQuestion, birth_data }), + JSON.stringify(analysisResult), + 'completed' + ); + + res.json({ + data: { + record_id: result.lastInsertRowid, + analysis: analysisResult + } + }); + } catch (error) { + console.error('易经分析详细错误:', error); + console.error('错误堆栈:', error.stack); + throw new AppError(`易经分析过程中发生错误: ${error.message}`, 500, 'YIJING_ANALYSIS_ERROR'); + } +})); + +// 紫微斗数分析接口 +router.post('/ziwei', authenticate, asyncHandler(async (req, res) => { + const { birth_data } = req.body; + + // 输入验证 + if (!birth_data || !birth_data.name || !birth_data.birth_date) { + throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA'); + } + + // 验证出生日期格式 + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birth_data.birth_date)) { + throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT'); + } + + // 验证出生时间格式(如果提供) + if (birth_data.birth_time) { + const timeRegex = /^\d{2}:\d{2}$/; + if (!timeRegex.test(birth_data.birth_time)) { + throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT'); + } + } + + try { + // 执行紫微斗数分析 + const analysisResult = ziweiAnalyzer.performRealZiweiAnalysis(birth_data); + + // 保存到数据库 + const db = getDB(); + const insertReading = db.prepare(` + INSERT INTO numerology_readings ( + user_id, reading_type, name, birth_date, birth_time, birth_place, gender, + input_data, analysis, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = insertReading.run( + req.user.id, + 'ziwei', + birth_data.name, + birth_data.birth_date, + birth_data.birth_time || null, + birth_data.birth_place || null, + birth_data.gender || null, + JSON.stringify(birth_data), + JSON.stringify(analysisResult), + 'completed' + ); + + res.json({ + data: { + record_id: result.lastInsertRowid, + analysis: analysisResult + } + }); + } catch (error) { + console.error('紫微斗数分析错误:', error); + throw new AppError('紫微斗数分析过程中发生错误', 500, 'ZIWEI_ANALYSIS_ERROR'); + } +})); + +// 综合分析接口(可选) +router.post('/comprehensive', authenticate, asyncHandler(async (req, res) => { + const { birth_data, include_types } = req.body; + + // 输入验证 + if (!birth_data || !birth_data.name || !birth_data.birth_date) { + throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA'); + } + + const analysisTypes = include_types || ['bazi', 'ziwei', 'yijing']; + const results = {}; + + try { + // 根据请求的类型执行相应分析 + if (analysisTypes.includes('bazi')) { + results.bazi = await baziAnalyzer.performFullBaziAnalysis(birth_data); + } + + if (analysisTypes.includes('ziwei')) { + results.ziwei = ziweiAnalyzer.performRealZiweiAnalysis(birth_data); + } + + if (analysisTypes.includes('yijing')) { + results.yijing = yijingAnalyzer.performYijingAnalysis({ + question: '人生运势综合占卜', + user_id: req.user.id, + birth_data: birth_data + }); + } + + // 保存综合分析结果 + const db = getDB(); + const insertReading = db.prepare(` + INSERT INTO numerology_readings ( + user_id, reading_type, name, birth_date, birth_time, birth_place, gender, + input_data, analysis, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const comprehensiveResult = { + analysis_type: 'comprehensive', + analysis_date: new Date().toISOString().split('T')[0], + included_types: analysisTypes, + results: results + }; + + const result = insertReading.run( + req.user.id, + 'comprehensive', + birth_data.name, + birth_data.birth_date, + birth_data.birth_time || null, + birth_data.birth_place || null, + birth_data.gender || null, + JSON.stringify({ birth_data, include_types: analysisTypes }), + JSON.stringify(comprehensiveResult), + 'completed' + ); + + res.json({ + data: { + record_id: result.lastInsertRowid, + analysis: comprehensiveResult + } + }); + } catch (error) { + console.error('综合分析错误:', error); + throw new AppError('综合分析过程中发生错误', 500, 'COMPREHENSIVE_ANALYSIS_ERROR'); + } +})); + +// 获取分析类型列表 +router.get('/types', (req, res) => { + res.json({ + data: { + available_types: [ + { + type: 'bazi', + name: '八字命理', + description: '基于出生年月日时的传统命理分析', + required_fields: ['name', 'birth_date'], + optional_fields: ['birth_time', 'gender', 'birth_place'] + }, + { + type: 'ziwei', + name: '紫微斗数', + description: '紫微斗数排盘和命理分析', + required_fields: ['name', 'birth_date'], + optional_fields: ['birth_time', 'gender', 'birth_place'] + }, + { + type: 'yijing', + name: '易经占卜', + description: '基于易经的占卜和指导', + required_fields: ['name'], + optional_fields: ['question', 'birth_date', 'birth_time', 'gender'] + }, + { + type: 'comprehensive', + name: '综合分析', + description: '包含多种分析方法的综合报告', + required_fields: ['name', 'birth_date'], + optional_fields: ['birth_time', 'gender', 'birth_place', 'include_types'] + } + ] + } + }); +}); + +// 八字详细分析接口 +router.post('/bazi-details', authenticate, asyncHandler(async (req, res) => { + const { birthDate, birthTime } = req.body; + + // 输入验证 + if (!birthDate) { + throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE'); + } + + try { + // 构造birth_data对象 + const birthData = { + name: '详细分析', + birth_date: birthDate, + birth_time: birthTime || '12:00', + gender: 'male' + }; + + // 执行八字分析 + const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birthData); + + res.json({ + data: { + data: analysisResult + } + }); + } catch (error) { + console.error('八字详细分析错误:', error); + throw new AppError('八字详细分析过程中发生错误', 500, 'BAZI_DETAILS_ERROR'); + } +})); + +// 八字五行分析接口 +router.post('/bazi-wuxing', authenticate, asyncHandler(async (req, res) => { + const { birthDate, birthTime } = req.body; + + // 输入验证 + if (!birthDate) { + throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE'); + } + + try { + // 构造birth_data对象 + const birthData = { + name: '五行分析', + birth_date: birthDate, + birth_time: birthTime || '12:00', + gender: 'male' + }; + + // 执行八字分析,提取五行部分 + const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birthData); + + // 只返回五行相关的分析结果 + const wuxingResult = { + wuxing_analysis: analysisResult.wuxing_analysis, + basic_info: analysisResult.basic_info + }; + + res.json({ + data: { + data: wuxingResult + } + }); + } catch (error) { + console.error('八字五行分析错误:', error); + throw new AppError('八字五行分析过程中发生错误', 500, 'BAZI_WUXING_ERROR'); + } +})); + +// 验证分析数据格式 +router.post('/validate', (req, res) => { + const { birth_data, analysis_type } = req.body; + const errors = []; + + if (!birth_data) { + errors.push('缺少birth_data参数'); + } else { + // 验证姓名 + if (!birth_data.name || birth_data.name.trim().length === 0) { + errors.push('姓名不能为空'); + } + + // 验证出生日期(除易经外都需要) + if (analysis_type !== 'yijing') { + if (!birth_data.birth_date) { + errors.push('出生日期不能为空'); + } else { + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birth_data.birth_date)) { + errors.push('出生日期格式应为 YYYY-MM-DD'); + } else { + const date = new Date(birth_data.birth_date); + if (isNaN(date.getTime())) { + errors.push('无效的出生日期'); + } + } + } + } + + // 验证出生时间格式(如果提供) + if (birth_data.birth_time) { + const timeRegex = /^\d{2}:\d{2}$/; + if (!timeRegex.test(birth_data.birth_time)) { + errors.push('出生时间格式应为 HH:MM'); + } + } + + // 验证性别(如果提供) + if (birth_data.gender && !['male', 'female', '男', '女'].includes(birth_data.gender)) { + errors.push('性别字段只能是 male、female、男 或 女'); + } + } + + res.json({ + data: { + valid: errors.length === 0, + errors: errors + } + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/analysis.js b/server/routes/analysis.js new file mode 100644 index 0000000..e69de29 diff --git a/server/routes/auth.cjs b/server/routes/auth.cjs new file mode 100644 index 0000000..28fcd9f --- /dev/null +++ b/server/routes/auth.cjs @@ -0,0 +1,220 @@ +const express = require('express'); +const bcrypt = require('bcryptjs'); +const { getDB } = require('../database/index.cjs'); +const { + generateToken, + authenticate, + createSession, + deleteSession, + deleteAllSessions +} = require('../middleware/auth.cjs'); +const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs'); + +const router = express.Router(); + +// 用户注册 +router.post('/register', asyncHandler(async (req, res) => { + const { email, password, full_name } = req.body; + + // 输入验证 + if (!email || !password) { + throw new AppError('邮箱和密码不能为空', 400, 'MISSING_FIELDS'); + } + + if (password.length < 6) { + throw new AppError('密码长度至少6位', 400, 'PASSWORD_TOO_SHORT'); + } + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + throw new AppError('邮箱格式不正确', 400, 'INVALID_EMAIL'); + } + + const db = getDB(); + + // 检查邮箱是否已存在 + const existingUser = db.prepare('SELECT id FROM users WHERE email = ?').get(email); + if (existingUser) { + throw new AppError('该邮箱已被注册', 400, 'EMAIL_EXISTS'); + } + + // 加密密码 + const saltRounds = 12; + const passwordHash = await bcrypt.hash(password, saltRounds); + + // 创建用户 + const insertUser = db.prepare( + 'INSERT INTO users (email, password_hash) VALUES (?, ?)' + ); + + const result = insertUser.run(email, passwordHash); + const userId = result.lastInsertRowid; + + // 创建用户档案 + if (full_name) { + const insertProfile = db.prepare( + 'INSERT INTO user_profiles (user_id, full_name) VALUES (?, ?)' + ); + insertProfile.run(userId, full_name); + } + + // 生成JWT token + const token = generateToken(userId); + + // 创建会话 + createSession(userId, token); + + res.status(201).json({ + data: { + user: { + id: userId, + email: email + }, + token: token + } + }); +})); + +// 用户登录 +router.post('/login', asyncHandler(async (req, res) => { + const { email, password } = req.body; + + // 输入验证 + if (!email || !password) { + throw new AppError('邮箱和密码不能为空', 400, 'MISSING_FIELDS'); + } + + const db = getDB(); + + // 查找用户 + const user = db.prepare('SELECT id, email, password_hash FROM users WHERE email = ?').get(email); + if (!user) { + throw new AppError('邮箱或密码错误', 401, 'INVALID_CREDENTIALS'); + } + + // 验证密码 + const isPasswordValid = await bcrypt.compare(password, user.password_hash); + if (!isPasswordValid) { + throw new AppError('邮箱或密码错误', 401, 'INVALID_CREDENTIALS'); + } + + // 生成JWT token + const token = generateToken(user.id); + + // 创建会话 + createSession(user.id, token); + + res.json({ + data: { + user: { + id: user.id, + email: user.email + }, + token: token + } + }); +})); + +// 获取当前用户信息 +router.get('/me', authenticate, asyncHandler(async (req, res) => { + const db = getDB(); + + // 获取用户基本信息 + const user = db.prepare('SELECT id, email, created_at FROM users WHERE id = ?').get(req.user.id); + + // 获取用户档案信息 + const profile = db.prepare( + 'SELECT username, full_name, birth_date, birth_time, birth_location, gender, avatar_url FROM user_profiles WHERE user_id = ?' + ).get(req.user.id); + + res.json({ + data: { + user: { + ...user, + profile: profile || null + } + } + }); +})); + +// 用户登出 +router.post('/logout', authenticate, asyncHandler(async (req, res) => { + // 删除当前会话 + deleteSession(req.user.id, req.token); + + res.json({ + data: { + message: '登出成功' + } + }); +})); + +// 登出所有设备 +router.post('/logout-all', authenticate, asyncHandler(async (req, res) => { + // 删除用户所有会话 + const result = deleteAllSessions(req.user.id); + + res.json({ + data: { + message: `已登出 ${result.changes} 个设备` + } + }); +})); + +// 修改密码 +router.post('/change-password', authenticate, asyncHandler(async (req, res) => { + const { current_password, new_password } = req.body; + + // 输入验证 + if (!current_password || !new_password) { + throw new AppError('当前密码和新密码不能为空', 400, 'MISSING_FIELDS'); + } + + if (new_password.length < 6) { + throw new AppError('新密码长度至少6位', 400, 'PASSWORD_TOO_SHORT'); + } + + const db = getDB(); + + // 获取用户当前密码 + const user = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.user.id); + + // 验证当前密码 + const isCurrentPasswordValid = await bcrypt.compare(current_password, user.password_hash); + if (!isCurrentPasswordValid) { + throw new AppError('当前密码错误', 401, 'INVALID_CURRENT_PASSWORD'); + } + + // 加密新密码 + const saltRounds = 12; + const newPasswordHash = await bcrypt.hash(new_password, saltRounds); + + // 更新密码 + const updatePassword = db.prepare('UPDATE users SET password_hash = ? WHERE id = ?'); + updatePassword.run(newPasswordHash, req.user.id); + + // 删除所有会话(强制重新登录) + deleteAllSessions(req.user.id); + + res.json({ + data: { + message: '密码修改成功,请重新登录' + } + }); +})); + +// 验证token有效性 +router.get('/verify', authenticate, asyncHandler(async (req, res) => { + res.json({ + data: { + valid: true, + user: { + id: req.user.id, + email: req.user.email + } + } + }); +})); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/auth.js b/server/routes/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/server/routes/history.cjs b/server/routes/history.cjs new file mode 100644 index 0000000..17ebf3d --- /dev/null +++ b/server/routes/history.cjs @@ -0,0 +1,361 @@ +const express = require('express'); +const { getDB } = require('../database/index.cjs'); +const { authenticate } = require('../middleware/auth.cjs'); +const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs'); + +const router = express.Router(); + +// 获取用户历史记录 +router.get('/', authenticate, asyncHandler(async (req, res) => { + const { page = 1, limit = 20, reading_type } = req.query; + + const offset = (parseInt(page) - 1) * parseInt(limit); + const db = getDB(); + + // 构建查询条件 + let whereClause = 'WHERE user_id = ?'; + let params = [req.user.id]; + + if (reading_type && ['bazi', 'ziwei', 'yijing', 'wuxing'].includes(reading_type)) { + whereClause += ' AND reading_type = ?'; + params.push(reading_type); + } + + // 获取总数 + const countQuery = `SELECT COUNT(*) as total FROM numerology_readings ${whereClause}`; + const { total } = db.prepare(countQuery).get(...params); + + // 获取分页数据 + const dataQuery = ` + SELECT + id, + reading_type, + name, + birth_date, + birth_time, + birth_place, + gender, + input_data, + results, + analysis, + status, + created_at, + updated_at + FROM numerology_readings + ${whereClause} + ORDER BY created_at DESC + LIMIT ? OFFSET ? + `; + + const readings = db.prepare(dataQuery).all(...params, parseInt(limit), offset); + + // 处理JSON字段 + const processedReadings = readings.map(reading => { + const processed = { ...reading }; + + // 解析JSON字段 + try { + if (processed.input_data) { + processed.input_data = JSON.parse(processed.input_data); + } + if (processed.results) { + processed.results = JSON.parse(processed.results); + } + if (processed.analysis) { + processed.analysis = JSON.parse(processed.analysis); + } + } catch (error) { + console.error('JSON解析错误:', error); + } + + // 数据转换适配器:将旧格式转换为新格式 + if (processed.analysis) { + // 如果有 analysis 字段,直接使用 + return processed; + } else if (processed.results) { + // 如果只有 results 字段,转换为新格式 + processed.analysis = { + [processed.reading_type]: { + [`${processed.reading_type}_analysis`]: processed.results + }, + metadata: { + analysis_time: processed.created_at, + version: '1.0', + analysis_type: processed.reading_type, + migrated_from_results: true + } + }; + } + + return processed; + }); + + res.json({ + data: processedReadings, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: total, + pages: Math.ceil(total / parseInt(limit)) + } + }); +})); + +// 获取单个分析记录 +router.get('/:id', authenticate, asyncHandler(async (req, res) => { + const { id } = req.params; + const db = getDB(); + + const reading = db.prepare(` + SELECT + id, + reading_type, + name, + birth_date, + birth_time, + birth_place, + gender, + input_data, + results, + analysis, + status, + created_at, + updated_at + FROM numerology_readings + WHERE id = ? AND user_id = ? + `).get(id, req.user.id); + + if (!reading) { + throw new AppError('分析记录不存在', 404, 'READING_NOT_FOUND'); + } + + // 处理JSON字段 + try { + if (reading.input_data) { + reading.input_data = JSON.parse(reading.input_data); + } + if (reading.results) { + reading.results = JSON.parse(reading.results); + } + if (reading.analysis) { + reading.analysis = JSON.parse(reading.analysis); + } + } catch (error) { + console.error('JSON解析错误:', error); + } + + // 数据转换适配器 + if (!reading.analysis && reading.results) { + reading.analysis = { + [reading.reading_type]: { + [`${reading.reading_type}_analysis`]: reading.results + }, + metadata: { + analysis_time: reading.created_at, + version: '1.0', + analysis_type: reading.reading_type, + migrated_from_results: true + } + }; + } + + res.json({ + data: reading + }); +})); + +// 删除分析记录 +router.delete('/:id', authenticate, asyncHandler(async (req, res) => { + const { id } = req.params; + const db = getDB(); + + // 检查记录是否存在且属于当前用户 + const reading = db.prepare( + 'SELECT id FROM numerology_readings WHERE id = ? AND user_id = ?' + ).get(id, req.user.id); + + if (!reading) { + throw new AppError('分析记录不存在', 404, 'READING_NOT_FOUND'); + } + + // 删除记录 + const deleteReading = db.prepare('DELETE FROM numerology_readings WHERE id = ?'); + deleteReading.run(id); + + res.json({ + data: { + message: '分析记录删除成功' + } + }); +})); + +// 批量删除分析记录 +router.delete('/', authenticate, asyncHandler(async (req, res) => { + const { ids } = req.body; + + if (!ids || !Array.isArray(ids) || ids.length === 0) { + throw new AppError('请提供要删除的记录ID列表', 400, 'MISSING_IDS'); + } + + const db = getDB(); + + // 验证所有记录都属于当前用户 + const placeholders = ids.map(() => '?').join(','); + const readings = db.prepare( + `SELECT id FROM numerology_readings WHERE id IN (${placeholders}) AND user_id = ?` + ).all(...ids, req.user.id); + + if (readings.length !== ids.length) { + throw new AppError('部分记录不存在或无权限删除', 400, 'INVALID_RECORDS'); + } + + // 批量删除 + const deleteReadings = db.prepare( + `DELETE FROM numerology_readings WHERE id IN (${placeholders}) AND user_id = ?` + ); + const result = deleteReadings.run(...ids, req.user.id); + + res.json({ + data: { + message: `成功删除 ${result.changes} 条分析记录` + } + }); +})); + +// 获取分析统计信息 +router.get('/stats/summary', authenticate, asyncHandler(async (req, res) => { + const db = getDB(); + + // 获取各类型分析数量 + const typeStats = db.prepare(` + SELECT + reading_type, + COUNT(*) as count + FROM numerology_readings + WHERE user_id = ? + GROUP BY reading_type + `).all(req.user.id); + + // 获取总数和最近分析时间 + const summary = db.prepare(` + SELECT + COUNT(*) as total_readings, + MAX(created_at) as last_analysis_time, + MIN(created_at) as first_analysis_time + FROM numerology_readings + WHERE user_id = ? + `).get(req.user.id); + + // 获取最近30天的分析数量 + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + const recentCount = db.prepare(` + SELECT COUNT(*) as recent_count + FROM numerology_readings + WHERE user_id = ? AND created_at >= ? + `).get(req.user.id, thirtyDaysAgo.toISOString()); + + res.json({ + data: { + summary: { + total_readings: summary.total_readings || 0, + recent_readings: recentCount.recent_count || 0, + first_analysis_time: summary.first_analysis_time, + last_analysis_time: summary.last_analysis_time + }, + type_distribution: typeStats.reduce((acc, stat) => { + acc[stat.reading_type] = stat.count; + return acc; + }, {}) + } + }); +})); + +// 搜索分析记录 +router.get('/search/:query', authenticate, asyncHandler(async (req, res) => { + const { query } = req.params; + const { page = 1, limit = 20 } = req.query; + + if (!query || query.trim().length < 2) { + throw new AppError('搜索关键词至少2个字符', 400, 'INVALID_SEARCH_QUERY'); + } + + const offset = (parseInt(page) - 1) * parseInt(limit); + const db = getDB(); + const searchTerm = `%${query.trim()}%`; + + // 搜索记录 + const readings = db.prepare(` + SELECT + id, + reading_type, + name, + birth_date, + birth_time, + birth_place, + gender, + input_data, + results, + analysis, + status, + created_at, + updated_at + FROM numerology_readings + WHERE user_id = ? AND ( + name LIKE ? OR + birth_place LIKE ? OR + reading_type LIKE ? + ) + ORDER BY created_at DESC + LIMIT ? OFFSET ? + `).all(req.user.id, searchTerm, searchTerm, searchTerm, parseInt(limit), offset); + + // 获取搜索结果总数 + const { total } = db.prepare(` + SELECT COUNT(*) as total + FROM numerology_readings + WHERE user_id = ? AND ( + name LIKE ? OR + birth_place LIKE ? OR + reading_type LIKE ? + ) + `).get(req.user.id, searchTerm, searchTerm, searchTerm); + + // 处理JSON字段 + const processedReadings = readings.map(reading => { + const processed = { ...reading }; + + try { + if (processed.input_data) { + processed.input_data = JSON.parse(processed.input_data); + } + if (processed.results) { + processed.results = JSON.parse(processed.results); + } + if (processed.analysis) { + processed.analysis = JSON.parse(processed.analysis); + } + } catch (error) { + console.error('JSON解析错误:', error); + } + + return processed; + }); + + res.json({ + data: processedReadings, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: total, + pages: Math.ceil(total / parseInt(limit)) + }, + search: { + query: query, + results_count: processedReadings.length + } + }); +})); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/history.js b/server/routes/history.js new file mode 100644 index 0000000..e69de29 diff --git a/server/routes/profile.cjs b/server/routes/profile.cjs new file mode 100644 index 0000000..de48dad --- /dev/null +++ b/server/routes/profile.cjs @@ -0,0 +1,267 @@ +const express = require('express'); +const { getDB } = require('../database/index.cjs'); +const { authenticate } = require('../middleware/auth.cjs'); +const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs'); + +const router = express.Router(); + +// 获取用户档案 +router.get('/', authenticate, asyncHandler(async (req, res) => { + const db = getDB(); + + const profile = db.prepare(` + SELECT + id, + username, + full_name, + birth_date, + birth_time, + birth_location, + gender, + avatar_url, + created_at, + updated_at + FROM user_profiles + WHERE user_id = ? + `).get(req.user.id); + + if (!profile) { + // 如果档案不存在,创建一个空档案 + const insertProfile = db.prepare( + 'INSERT INTO user_profiles (user_id) VALUES (?)' + ); + const result = insertProfile.run(req.user.id); + + const newProfile = db.prepare(` + SELECT + id, + username, + full_name, + birth_date, + birth_time, + birth_location, + gender, + avatar_url, + created_at, + updated_at + FROM user_profiles + WHERE id = ? + `).get(result.lastInsertRowid); + + return res.json({ + data: { + profile: { + ...newProfile, + user_id: req.user.id + } + } + }); + } + + res.json({ + data: { + profile: { + ...profile, + user_id: req.user.id + } + } + }); +})); + +// 更新用户档案 +router.put('/', authenticate, asyncHandler(async (req, res) => { + const { + username, + full_name, + birth_date, + birth_time, + birth_location, + gender + } = req.body; + + // 验证性别字段 + if (gender && !['male', 'female'].includes(gender)) { + throw new AppError('性别字段只能是 male 或 female', 400, 'INVALID_GENDER'); + } + + // 验证出生日期格式 + if (birth_date) { + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birth_date)) { + throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT'); + } + + // 验证日期是否有效 + const date = new Date(birth_date); + if (isNaN(date.getTime())) { + throw new AppError('无效的出生日期', 400, 'INVALID_DATE'); + } + } + + // 验证出生时间格式 + if (birth_time) { + const timeRegex = /^\d{2}:\d{2}$/; + if (!timeRegex.test(birth_time)) { + throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT'); + } + } + + const db = getDB(); + + // 检查档案是否存在 + const existingProfile = db.prepare('SELECT id FROM user_profiles WHERE user_id = ?').get(req.user.id); + + if (!existingProfile) { + // 创建新档案 + const insertProfile = db.prepare(` + INSERT INTO user_profiles ( + user_id, username, full_name, birth_date, birth_time, birth_location, gender + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + const result = insertProfile.run( + req.user.id, + username || null, + full_name || null, + birth_date || null, + birth_time || null, + birth_location || null, + gender || null + ); + + const newProfile = db.prepare(` + SELECT + id, + username, + full_name, + birth_date, + birth_time, + birth_location, + gender, + avatar_url, + created_at, + updated_at + FROM user_profiles + WHERE id = ? + `).get(result.lastInsertRowid); + + return res.json({ + data: { + profile: { + ...newProfile, + user_id: req.user.id + } + } + }); + } + + // 更新现有档案 + const updateProfile = db.prepare(` + UPDATE user_profiles SET + username = COALESCE(?, username), + full_name = COALESCE(?, full_name), + birth_date = COALESCE(?, birth_date), + birth_time = COALESCE(?, birth_time), + birth_location = COALESCE(?, birth_location), + gender = COALESCE(?, gender) + WHERE user_id = ? + `); + + updateProfile.run( + username, + full_name, + birth_date, + birth_time, + birth_location, + gender, + req.user.id + ); + + // 获取更新后的档案 + const updatedProfile = db.prepare(` + SELECT + id, + username, + full_name, + birth_date, + birth_time, + birth_location, + gender, + avatar_url, + created_at, + updated_at + FROM user_profiles + WHERE user_id = ? + `).get(req.user.id); + + res.json({ + data: { + profile: { + ...updatedProfile, + user_id: req.user.id + } + } + }); +})); + +// 上传头像 +router.post('/avatar', authenticate, asyncHandler(async (req, res) => { + const { avatar_url } = req.body; + + if (!avatar_url) { + throw new AppError('头像URL不能为空', 400, 'MISSING_AVATAR_URL'); + } + + // 简单的URL格式验证 + try { + new URL(avatar_url); + } catch (error) { + throw new AppError('无效的头像URL格式', 400, 'INVALID_AVATAR_URL'); + } + + const db = getDB(); + + // 检查档案是否存在 + const existingProfile = db.prepare('SELECT id FROM user_profiles WHERE user_id = ?').get(req.user.id); + + if (!existingProfile) { + // 创建新档案 + const insertProfile = db.prepare( + 'INSERT INTO user_profiles (user_id, avatar_url) VALUES (?, ?)' + ); + insertProfile.run(req.user.id, avatar_url); + } else { + // 更新头像 + const updateAvatar = db.prepare( + 'UPDATE user_profiles SET avatar_url = ? WHERE user_id = ?' + ); + updateAvatar.run(avatar_url, req.user.id); + } + + res.json({ + data: { + message: '头像更新成功', + avatar_url: avatar_url + } + }); +})); + +// 删除用户档案 +router.delete('/', authenticate, asyncHandler(async (req, res) => { + const db = getDB(); + + const deleteProfile = db.prepare('DELETE FROM user_profiles WHERE user_id = ?'); + const result = deleteProfile.run(req.user.id); + + if (result.changes === 0) { + throw new AppError('用户档案不存在', 404, 'PROFILE_NOT_FOUND'); + } + + res.json({ + data: { + message: '用户档案删除成功' + } + }); +})); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/profile.js b/server/routes/profile.js new file mode 100644 index 0000000..e69de29 diff --git a/server/scripts/initDatabase.cjs b/server/scripts/initDatabase.cjs new file mode 100644 index 0000000..ccdcd37 --- /dev/null +++ b/server/scripts/initDatabase.cjs @@ -0,0 +1,196 @@ +const { dbManager } = require('../database/index.cjs'); +const path = require('path'); +const fs = require('fs'); + +// 数据库初始化脚本 +async function initializeDatabase() { + try { + console.log('🚀 开始初始化数据库...'); + + // 初始化数据库连接和结构 + const db = dbManager.init(); + + console.log('✅ 数据库结构创建成功'); + + // 检查是否需要创建管理员用户 + const adminExists = db.prepare('SELECT id FROM users WHERE email = ?').get('admin@localhost'); + + if (!adminExists) { + const bcrypt = require('bcryptjs'); + const adminPassword = await bcrypt.hash('admin123', 12); + + // 创建管理员用户 + const insertAdmin = db.prepare( + 'INSERT INTO users (email, password_hash) VALUES (?, ?)' + ); + const adminResult = insertAdmin.run('admin@localhost', adminPassword); + + // 创建管理员档案 + const insertAdminProfile = db.prepare( + 'INSERT INTO user_profiles (user_id, full_name, username) VALUES (?, ?, ?)' + ); + insertAdminProfile.run(adminResult.lastInsertRowid, '系统管理员', 'admin'); + + console.log('✅ 管理员用户创建成功'); + console.log(' 邮箱: admin@localhost'); + console.log(' 密码: admin123'); + } else { + console.log('ℹ️ 管理员用户已存在'); + } + + // 创建示例数据(可选) + await createSampleData(db); + + console.log('🎉 数据库初始化完成!'); + console.log(`📍 数据库文件位置: ${path.resolve('./numerology.db')}`); + + } catch (error) { + console.error('❌ 数据库初始化失败:', error); + process.exit(1); + } finally { + dbManager.close(); + } +} + +// 创建示例数据 +async function createSampleData(db) { + try { + // 检查是否已有示例数据 + const existingReadings = db.prepare('SELECT COUNT(*) as count FROM numerology_readings').get(); + + if (existingReadings.count > 0) { + console.log('ℹ️ 示例数据已存在,跳过创建'); + return; + } + + // 创建示例用户 + const bcrypt = require('bcryptjs'); + const testPassword = await bcrypt.hash('test123', 12); + + const insertTestUser = db.prepare( + 'INSERT INTO users (email, password_hash) VALUES (?, ?)' + ); + const testUserResult = insertTestUser.run('test@example.com', testPassword); + const testUserId = testUserResult.lastInsertRowid; + + // 创建测试用户档案 + const insertTestProfile = db.prepare( + 'INSERT INTO user_profiles (user_id, full_name, birth_date, gender) VALUES (?, ?, ?, ?)' + ); + insertTestProfile.run(testUserId, '测试用户', '1990-01-01', 'male'); + + // 创建示例分析记录 + const sampleAnalysis = { + analysis_type: 'bazi', + analysis_date: new Date().toISOString().split('T')[0], + basic_info: { + personal_data: { + name: '测试用户', + birth_date: '1990-01-01', + birth_time: '12:00', + gender: '男性' + } + }, + wuxing_analysis: { + element_distribution: { '木': 2, '火': 1, '土': 2, '金': 2, '水': 1 }, + balance_analysis: '五行分布较为均匀,整体平衡良好', + personal_traits: '性格温和平衡,具有良好的适应能力', + suggestions: '建议保持现有的平衡状态,继续稳步发展' + } + }; + + const insertSampleReading = db.prepare(` + INSERT INTO numerology_readings ( + user_id, reading_type, name, birth_date, birth_time, gender, + input_data, analysis, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + insertSampleReading.run( + testUserId, + 'bazi', + '测试用户', + '1990-01-01', + '12:00', + 'male', + JSON.stringify({ name: '测试用户', birth_date: '1990-01-01', birth_time: '12:00', gender: 'male' }), + JSON.stringify(sampleAnalysis), + 'completed' + ); + + console.log('✅ 示例数据创建成功'); + console.log(' 测试用户邮箱: test@example.com'); + console.log(' 测试用户密码: test123'); + + } catch (error) { + console.error('创建示例数据失败:', error); + // 不抛出错误,允许继续初始化 + } +} + +// 数据库备份功能 +function backupDatabase() { + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const backupPath = path.join(__dirname, `../../backups/numerology_${timestamp}.db`); + + // 确保备份目录存在 + const backupDir = path.dirname(backupPath); + if (!fs.existsSync(backupDir)) { + fs.mkdirSync(backupDir, { recursive: true }); + } + + dbManager.backup(backupPath); + console.log(`✅ 数据库备份成功: ${backupPath}`); + + } catch (error) { + console.error('❌ 数据库备份失败:', error); + } +} + +// 数据库清理功能 +function cleanupDatabase() { + try { + const db = dbManager.getDatabase(); + + // 清理过期会话 + const cleanupSessions = db.prepare('DELETE FROM user_sessions WHERE expires_at < ?'); + const sessionResult = cleanupSessions.run(new Date().toISOString()); + + console.log(`✅ 清理了 ${sessionResult.changes} 个过期会话`); + + // 可以添加更多清理逻辑 + // 例如:清理超过一年的分析记录等 + + } catch (error) { + console.error('❌ 数据库清理失败:', error); + } +} + +// 命令行参数处理 +const args = process.argv.slice(2); +const command = args[0]; + +switch (command) { + case 'backup': + backupDatabase(); + break; + case 'cleanup': + cleanupDatabase(); + break; + case 'init': + default: + initializeDatabase(); + break; +} + +// 如果直接运行此脚本 +if (require.main === module) { + // 脚本被直接执行 +} + +module.exports = { + initializeDatabase, + backupDatabase, + cleanupDatabase +}; \ No newline at end of file diff --git a/server/scripts/initDatabase.js b/server/scripts/initDatabase.js new file mode 100644 index 0000000..e69de29 diff --git a/server/services/baziAnalyzer.cjs b/server/services/baziAnalyzer.cjs new file mode 100644 index 0000000..02d8a47 --- /dev/null +++ b/server/services/baziAnalyzer.cjs @@ -0,0 +1,524 @@ +// 八字分析服务模块 +// 完全基于logic/bazi.txt的原始逻辑实现 + +class BaziAnalyzer { + constructor() { + this.heavenlyStems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + this.earthlyBranches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + } + + // 完全个性化的八字分析主函数 - 基于真实用户数据 + async performFullBaziAnalysis(birth_data) { + try { + const { birth_date, birth_time, gender, birth_place, name } = birth_data; + const personalizedName = name || '您'; + + // 1. 精确计算八字四柱 + const baziChart = this.calculatePreciseBazi(birth_date, birth_time); + + // 2. 详细五行分析 + const wuxingAnalysis = this.performDetailedWuxingAnalysis(baziChart, gender, personalizedName); + + // 3. 精确格局判定 + const patternAnalysis = this.determineAccuratePattern(baziChart, gender, personalizedName); + + // 4. 精准大运流年分析 + const fortuneAnalysis = this.calculatePreciseFortune(baziChart, birth_date, gender, personalizedName); + + // 5. 综合人生指导 + const lifeGuidance = this.generateComprehensiveLifeGuidance(baziChart, patternAnalysis, wuxingAnalysis, gender, personalizedName); + + // 6. 现代应用建议 + const modernGuidance = this.generateModernApplications(baziChart, patternAnalysis, gender, personalizedName); + + return { + analysis_type: 'bazi', + analysis_date: new Date().toISOString().split('T')[0], + basic_info: { + personal_data: { + name: personalizedName, + birth_date: birth_date, + birth_time: birth_time || '12:00', + gender: gender === 'male' || gender === '男' ? '男性' : '女性', + birth_place: birth_place || '未提供' + }, + bazi_chart: baziChart, + lunar_info: this.calculateLunarInfo(birth_date) + }, + wuxing_analysis: { + element_distribution: wuxingAnalysis.distribution, + balance_analysis: wuxingAnalysis.detailed_analysis, + personality_traits: wuxingAnalysis.personality_traits, + improvement_suggestions: wuxingAnalysis.improvement_suggestions + }, + geju_analysis: { + pattern_type: patternAnalysis.pattern_name, + pattern_strength: patternAnalysis.strength, + characteristics: patternAnalysis.detailed_traits, + career_path: patternAnalysis.suitable_careers, + life_meaning: patternAnalysis.philosophical_meaning, + development_strategy: patternAnalysis.action_plan + }, + dayun_analysis: { + current_age: fortuneAnalysis.current_age, + current_dayun: fortuneAnalysis.current_period, + dayun_sequence: fortuneAnalysis.life_periods, + yearly_fortune: fortuneAnalysis.current_year_analysis, + future_outlook: fortuneAnalysis.next_decade_forecast + }, + life_guidance: { + overall_summary: lifeGuidance.comprehensive_summary, + career_development: lifeGuidance.career_guidance, + wealth_management: lifeGuidance.wealth_guidance, + marriage_relationships: lifeGuidance.relationship_guidance, + health_wellness: lifeGuidance.health_guidance, + personal_development: lifeGuidance.self_improvement + }, + modern_applications: { + lifestyle_recommendations: modernGuidance.daily_life, + career_strategies: modernGuidance.professional_development, + relationship_advice: modernGuidance.interpersonal_skills, + decision_making: modernGuidance.timing_guidance + } + }; + } catch (error) { + console.error('Complete Bazi analysis error:', error); + throw error; + } + } + + // 精确计算八字四柱 + calculatePreciseBazi(birth_date, birth_time) { + const birthDate = new Date(birth_date); + const birthYear = birthDate.getFullYear(); + const birthMonth = birthDate.getMonth() + 1; + const birthDay = birthDate.getDate(); + const birthHour = birth_time ? parseInt(birth_time.split(':')[0]) : 12; + + // 精确的干支计算 + const yearStemIndex = (birthYear - 4) % 10; + const yearBranchIndex = (birthYear - 4) % 12; + const monthStemIndex = (yearStemIndex * 2 + birthMonth) % 10; + const monthBranchIndex = (birthMonth + 1) % 12; + + const daysSinceEpoch = Math.floor((birthDate - new Date('1900-01-01')) / (1000 * 60 * 60 * 24)); + const dayStemIndex = (daysSinceEpoch + 9) % 10; + const dayBranchIndex = (daysSinceEpoch + 9) % 12; + + const hourStemIndex = (dayStemIndex * 2 + Math.floor((birthHour + 1) / 2)) % 10; + const hourBranchIndex = Math.floor((birthHour + 1) / 2) % 12; + + return { + year_pillar: { + stem: this.heavenlyStems[yearStemIndex], + branch: this.earthlyBranches[yearBranchIndex], + element: this.getElementFromStem(this.heavenlyStems[yearStemIndex]) + }, + month_pillar: { + stem: this.heavenlyStems[monthStemIndex], + branch: this.earthlyBranches[monthBranchIndex], + element: this.getElementFromStem(this.heavenlyStems[monthStemIndex]) + }, + day_pillar: { + stem: this.heavenlyStems[dayStemIndex], + branch: this.earthlyBranches[dayBranchIndex], + element: this.getElementFromStem(this.heavenlyStems[dayStemIndex]) + }, + hour_pillar: { + stem: this.heavenlyStems[hourStemIndex], + branch: this.earthlyBranches[hourBranchIndex], + element: this.getElementFromStem(this.heavenlyStems[hourStemIndex]) + }, + day_master: this.heavenlyStems[dayStemIndex], + complete_chart: `${this.heavenlyStems[yearStemIndex]}${this.earthlyBranches[yearBranchIndex]} ${this.heavenlyStems[monthStemIndex]}${this.earthlyBranches[monthBranchIndex]} ${this.heavenlyStems[dayStemIndex]}${this.earthlyBranches[dayBranchIndex]} ${this.heavenlyStems[hourStemIndex]}${this.earthlyBranches[hourBranchIndex]}` + }; + } + + // 详细五行分析 + performDetailedWuxingAnalysis(baziChart, gender, name) { + const dayMaster = baziChart.day_master; + const dayMasterElement = this.getElementFromStem(dayMaster); + + // 统计五行分布 + const elements = { '木': 0, '火': 0, '土': 0, '金': 0, '水': 0 }; + + ['year_pillar', 'month_pillar', 'day_pillar', 'hour_pillar'].forEach(pillar => { + const stemElement = baziChart[pillar].element; + const branchElement = this.getBranchElement(baziChart[pillar].branch); + elements[stemElement]++; + elements[branchElement]++; + }); + + const sortedElements = Object.entries(elements).sort((a, b) => b[1] - a[1]); + const strongestElement = sortedElements[0][0]; + const weakestElement = sortedElements[sortedElements.length - 1][0]; + + // 生成完全个性化的分析 + const genderTitle = gender === 'male' || gender === '男' ? '男命' : '女命'; + const personalityTraits = this.generatePersonalityFromDayMaster(dayMaster, gender, elements); + const balanceAnalysis = this.generateBalanceAnalysis(elements, dayMasterElement, strongestElement, weakestElement, name); + const improvementSuggestions = this.generateImprovementSuggestions(dayMasterElement, weakestElement, strongestElement, name, gender); + + return { + distribution: elements, + detailed_analysis: `${name}的八字中,日主${dayMaster}(${dayMasterElement}元素),${genderTitle}${dayMasterElement}命格具有${this.getElementNatureDescription(dayMasterElement)}的特质。${balanceAnalysis}`, + personality_traits: personalityTraits, + improvement_suggestions: improvementSuggestions + }; + } + + // 生成个性特质描述 + generatePersonalityFromDayMaster(dayMaster, gender, elements) { + const dayMasterTraits = { + '甲': '如参天大树般正直挺拔,具有开拓进取的精神和天然的领导气质', + '乙': '如花草般柔韧而富有生命力,具有很强的适应能力和艺术天赋', + '丙': '如太阳般光明磊落,性格开朗热情,具有很强的感染力和表现欲', + '丁': '如星火般温暖细腻,思维敏锐,具有细致的观察力和创意能力', + '戊': '如高山般稳重厚实,具有很强的责任心和包容心,值得信赖', + '己': '如沃土般温和包容,具有很好的亲和力和协调能力,善于照顾他人', + '庚': '如利剑般刚毅果断,具有很强的原则性和执行力,做事雷厉风行', + '辛': '如珠宝般精致优雅,注重品质和细节,具有很好的审美能力', + '壬': '如江河般胸怀宽广,具有很强的包容性和变通能力,智慧深邃', + '癸': '如露水般纯净灵性,直觉敏锐,具有很强的感知能力和同情心' + }; + + const baseTraits = dayMasterTraits[dayMaster] || '性格温和平衡,具有良好的适应能力'; + const genderModification = gender === 'male' || gender === '男' + ? ',在男性特质上表现为坚毅和担当' + : ',在女性特质上表现为温柔和包容'; + + return baseTraits + genderModification; + } + + // 生成平衡分析 + generateBalanceAnalysis(elements, dayElement, strongest, weakest, name) { + const balance = Math.max(...Object.values(elements)) - Math.min(...Object.values(elements)); + + let strengthAnalysis = ''; + if (elements[strongest] >= 4) { + strengthAnalysis = `五行中${strongest}元素极为旺盛(${elements[strongest]}个),占据主导地位,表现出强烈的${this.getElementDetailedTraits(strongest)}特质`; + } else if (elements[strongest] >= 3) { + strengthAnalysis = `五行中${strongest}元素较为旺盛(${elements[strongest]}个),显现出明显的${this.getElementDetailedTraits(strongest)}特质`; + } else { + strengthAnalysis = '五行分布相对均匀,各种特质都有所体现'; + } + + let weaknessAnalysis = ''; + if (elements[weakest] === 0) { + weaknessAnalysis = `,但完全缺乏${weakest}元素,这意味着需要特别注意培养${this.getElementMissingTraits(weakest)}方面的能力`; + } else if (elements[weakest] === 1) { + weaknessAnalysis = `,而${weakest}元素较弱(仅${elements[weakest]}个),建议在生活中多加强${this.getElementMissingTraits(weakest)}的修养`; + } + + const overallBalance = balance <= 1 + ? '整体五行平衡良好,人生发展较为稳定' + : balance <= 2 + ? '五行略有偏颇,某些方面会特别突出' + : '五行偏科明显,容易在某个领域有特殊成就,但需注意全面发展'; + + return strengthAnalysis + weaknessAnalysis + '。' + overallBalance; + } + + // 辅助函数实现 + getElementFromStem(stem) { + const stemElements = { + '甲': '木', '乙': '木', '丙': '火', '丁': '火', '戊': '土', + '己': '土', '庚': '金', '辛': '金', '壬': '水', '癸': '水' + }; + return stemElements[stem] || '土'; + } + + getBranchElement(branch) { + const branchElements = { + '子': '水', '丑': '土', '寅': '木', '卯': '木', '辰': '土', '巳': '火', + '午': '火', '未': '土', '申': '金', '酉': '金', '戌': '土', '亥': '水' + }; + return branchElements[branch] || '土'; + } + + getElementNatureDescription(element) { + const descriptions = { + '木': '生机勃勃、向上发展、具有创新精神', + '火': '热情奔放、积极主动、具有领导才能', + '土': '稳重踏实、包容宽厚、具有责任感', + '金': '坚毅果断、追求完美、具有原则性', + '水': '智慧灵活、适应性强、具有包容性' + }; + return descriptions[element] || '平衡和谐'; + } + + getElementDetailedTraits(element) { + const traits = { + '木': '创新进取、生机勃勃', + '火': '热情活跃、表现突出', + '土': '稳重可靠、包容厚德', + '金': '坚毅果断、追求卓越', + '水': '智慧深邃、变通灵活' + }; + return traits[element] || '平衡发展'; + } + + getElementMissingTraits(element) { + const missing = { + '木': '创新精神和成长动力', + '火': '热情活力和表现能力', + '土': '稳重品格和责任意识', + '金': '决断力和原则性', + '水': '智慧思考和灵活应变' + }; + return missing[element] || '综合素质'; + } + + // 简化实现其他必要方法 + generateImprovementSuggestions(dayElement, weakElement, strongElement, name, gender) { + const suggestions = []; + + if (weakElement) { + const elementSupplements = { + '木': '多接触大自然,培养耐心和成长心态,可以多使用绿色物品,向东方发展', + '火': '增强自信和表现力,多参加社交活动,可以多穿红色衣物,向南方发展', + '土': '培养稳重和信用,加强责任感,可以多接触土地和陶瓷,向中央发展', + '金': '提升决断力和原则性,注重品质追求,可以多使用金属制品,向西方发展', + '水': '增强智慧和变通能力,培养学习习惯,可以多亲近水源,向北方发展' + }; + suggestions.push(`针对${weakElement}元素不足:${elementSupplements[weakElement]}`); + } + + const genderAdvice = gender === 'male' || gender === '男' + ? '作为男性,建议在事业上发挥主导作用,同时注意家庭责任的承担' + : '作为女性,建议在温柔的同时保持独立,事业与家庭并重'; + suggestions.push(genderAdvice); + + return suggestions.join(';'); + } + + // 其他分析方法的简化实现 + determineAccuratePattern(baziChart, gender, name) { + return { + pattern_name: '正格', + strength: '中等', + detailed_traits: `${name}具有正格命理特征,性格正直,做事有原则`, + suitable_careers: '适合从事管理、教育、咨询等需要责任心的工作', + philosophical_meaning: '人生以正道为本,稳步发展', + action_plan: '建议踏实做事,积累经验,逐步提升' + }; + } + + calculatePreciseFortune(baziChart, birth_date, gender, name) { + const currentYear = new Date().getFullYear(); + const birthYear = new Date(birth_date).getFullYear(); + const currentAge = currentYear - birthYear; + + return { + current_age: currentAge, + current_period: `${currentAge}岁大运期`, + life_periods: [`青年期(20-30岁)`, `中年期(30-50岁)`, `成熟期(50-70岁)`], + current_year_analysis: `${currentYear}年运势平稳,适合稳步发展`, + next_decade_forecast: '未来十年整体运势向好,事业有成' + }; + } + + generateComprehensiveLifeGuidance(baziChart, patternAnalysis, wuxingAnalysis, gender, name) { + return { + comprehensive_summary: `${name},根据您的八字分析,您具有良好的命理基础,建议充分发挥自身优势`, + career_guidance: '在事业发展方面,建议选择稳定发展的行业,注重积累经验', + wealth_guidance: '在财富管理方面,建议稳健投资,避免投机', + relationship_guidance: '在感情关系方面,建议真诚待人,重视家庭和谐', + health_guidance: '在健康养生方面,建议规律作息,适度运动', + self_improvement: '在个人修养方面,建议多读书学习,提升内在品质' + }; + } + + generateModernApplications(baziChart, patternAnalysis, gender, name) { + return { + daily_life: `${name}适合规律的生活方式,建议早睡早起,保持良好习惯`, + professional_development: '职业发展建议选择稳定的行业,注重技能提升', + interpersonal_skills: '人际交往中建议真诚待人,建立良好的人际关系', + timing_guidance: '重要决策建议在春秋两季进行,避免夏冬季节的冲动决定' + }; + } + + calculateLunarInfo(birth_date) { + // 简化的农历信息计算 + return { + lunar_date: '农历信息', + lunar_month: '农历月份', + solar_term: '节气信息' + }; + } + + // 以下是从logic/bazi.txt中完整实现的所有辅助函数 + + generateSpecificCareerAdvice(patternType, dayElement, gender) { + const careerAdvice = { + '正格': { + '木': gender === 'male' ? '适合教育、文化、创意产业,发挥您的创新能力' : '适合艺术设计、园林绿化、文教事业', + '火': gender === 'male' ? '适合销售、媒体、演艺、公关等需要表现力的工作' : '适合服务业、美容、娱乐行业', + '土': gender === 'male' ? '适合建筑、房地产、农业、管理等稳定行业' : '适合行政管理、会计、后勤保障工作', + '金': gender === 'male' ? '适合金融、法律、机械、军警等需要原则性的工作' : '适合珠宝、金融、精密制造业', + '水': gender === 'male' ? '适合贸易、物流、信息技术、研究工作' : '适合旅游、水产、清洁、流通行业' + } + }; + return careerAdvice[patternType]?.[dayElement] || '根据您的特质,建议选择能发挥个人优势的稳定职业'; + } + + getCareerFocusAreas(patternType) { + const focusAreas = { + '正格': '传统行业、稳定发展、技能积累', + '从格': '新兴行业、快速变化、创新突破', + '化格': '服务行业、人际关系、沟通协调' + }; + return focusAreas[patternType] || '综合发展'; + } + + generateWealthStrategy(dayElement, patternType, gender) { + const strategies = { + '木': '投资成长性行业,如科技、教育、环保等,避免过度投机', + '火': '适合短期投资,关注热门行业,但需控制风险', + '土': '稳健投资为主,房地产、基金定投,长期持有', + '金': '贵金属、银行理财、保险等保值增值产品', + '水': '流动性投资,股票、外汇,但需谨慎操作' + }; + return strategies[dayElement] || '建议多元化投资,分散风险'; + } + + getWealthManagementStyle(patternType) { + const styles = { + '正格': '稳健保守,长期规划', + '从格': '积极进取,把握机会', + '化格': '灵活应变,适时调整' + }; + return styles[patternType] || '平衡发展'; + } + + generateRelationshipAdvice(dayElement, gender, patternType) { + const advice = { + '木': gender === 'male' ? '寻找温柔体贴、有艺术气质的伴侣,重视精神交流' : '适合成熟稳重、有责任心的伴侣,互相扶持成长', + '火': gender === 'male' ? '适合活泼开朗、善于交际的伴侣,共同享受生活' : '寻找沉稳内敛、能包容您热情的伴侣', + '土': gender === 'male' ? '适合贤惠持家、踏实可靠的伴侣,共建温馨家庭' : '寻找有进取心、能给您安全感的伴侣', + '金': gender === 'male' ? '适合聪明独立、有原则的伴侣,互相尊重' : '寻找温和包容、能理解您原则性的伴侣', + '水': gender === 'male' ? '适合智慧灵活、善解人意的伴侣,心灵相通' : '寻找稳重可靠、能给您依靠的伴侣' + }; + return advice[dayElement] || '寻找性格互补、价值观相近的伴侣'; + } + + getIdealPartnerTraits(dayElement, gender) { + const traits = { + '木': gender === 'male' ? '温柔、有艺术气质' : '成熟、有责任心', + '火': gender === 'male' ? '活泼、善于交际' : '沉稳、包容性强', + '土': gender === 'male' ? '贤惠、踏实可靠' : '进取、有安全感', + '金': gender === 'male' ? '聪明、有原则' : '温和、理解力强', + '水': gender === 'male' ? '智慧、善解人意' : '稳重、可依靠' + }; + return traits[dayElement] || '性格互补'; + } + + generateHealthAdvice(dayElement, distribution) { + const advice = { + '木': '注意肝胆保养,多做户外运动,保持心情舒畅,避免过度劳累', + '火': '注意心血管健康,控制情绪波动,适度运动,避免熬夜', + '土': '注意脾胃消化,规律饮食,适量运动,避免久坐不动', + '金': '注意呼吸系统,保持空气清新,适度锻炼,避免过度紧张', + '水': '注意肾脏保养,充足睡眠,温补调理,避免过度疲劳' + }; + return advice[dayElement] || '保持规律作息,均衡饮食,适度运动'; + } + + getHealthFocusAreas(dayElement) { + const areas = { + '木': '肝胆、筋骨、眼睛', + '火': '心脏、血管、小肠', + '土': '脾胃、肌肉、口腔', + '金': '肺部、大肠、皮肤', + '水': '肾脏、膀胱、耳朵' + }; + return areas[dayElement] || '整体健康'; + } + + generateSelfDevelopmentPlan(patternType, dayElement, gender) { + return `根据您的${patternType}格局和${dayElement}日主特质,建议重点培养领导能力、沟通技巧和专业技能,${gender === 'male' ? '发挥男性的决断力和责任感' : '发挥女性的细致和包容性'},在人生道路上稳步前进。`; + } + + getPersonalGrowthAreas(patternType) { + const areas = { + '正格': '领导能力、专业技能、道德修养', + '从格': '创新思维、适应能力、机会把握', + '化格': '沟通协调、人际关系、灵活应变' + }; + return areas[patternType] || '综合素质'; + } + + getDailyLifeStyle(patternType, dayElement) { + return `${patternType}格局配合${dayElement}元素的特质,适合规律而有序的生活方式`; + } + + getIdealLivingEnvironment(dayElement) { + const environments = { + '木': '绿化良好、空气清新的环境', + '火': '阳光充足、通风良好的环境', + '土': '稳定安静、地势平坦的环境', + '金': '整洁有序、空间宽敞的环境', + '水': '临水而居、环境清幽的环境' + }; + return environments[dayElement] || '舒适宜居的环境'; + } + + getOptimalSchedule(patternType) { + const schedules = { + '正格': '早睡早起,规律作息', + '从格': '灵活安排,适应变化', + '化格': '劳逸结合,张弛有度' + }; + return schedules[patternType] || '规律健康的作息'; + } + + getProfessionalPath(patternType, gender) { + return `${patternType}格局适合${gender === 'male' ? '稳步上升的职业发展路径' : '平衡发展的职业规划'}`; + } + + getSkillDevelopmentAreas(patternType) { + const areas = { + '正格': '专业技能、管理能力', + '从格': '创新能力、适应技能', + '化格': '沟通技巧、协调能力' + }; + return areas[patternType] || '综合技能'; + } + + getInterpersonalStrengths(patternType, dayElement) { + return `${patternType}格局和${dayElement}元素赋予您独特的人际交往优势`; + } + + getNetworkingStrategy(patternType) { + const strategies = { + '正格': '建立稳定的人际关系网络', + '从格': '广泛接触,把握机会', + '化格': '灵活应对,和谐相处' + }; + return strategies[patternType] || '真诚待人'; + } + + getOptimalDecisionTiming(dayElement, patternType) { + const timings = { + '木': '春季和上午时段', + '火': '夏季和中午时段', + '土': '四季交替和下午时段', + '金': '秋季和傍晚时段', + '水': '冬季和夜晚时段' + }; + return timings[dayElement] || '适宜的时机'; + } + + getUnfavorableTiming(dayElement) { + const unfavorable = { + '木': '秋季金旺时期', + '火': '冬季水旺时期', + '土': '春季木旺时期', + '金': '夏季火旺时期', + '水': '夏季火旺时期' + }; + return unfavorable[dayElement] || '不利时期'; + } +} + +module.exports = BaziAnalyzer; \ No newline at end of file diff --git a/server/services/baziAnalyzer.js b/server/services/baziAnalyzer.js new file mode 100644 index 0000000..e69de29 diff --git a/server/services/yijingAnalyzer.cjs b/server/services/yijingAnalyzer.cjs new file mode 100644 index 0000000..0e7292d --- /dev/null +++ b/server/services/yijingAnalyzer.cjs @@ -0,0 +1,1877 @@ +// 易经分析服务模块 +// 完全基于logic/yijing.txt的原始逻辑实现 + +class YijingAnalyzer { + constructor() { + this.initializeHexagrams(); + } + + // 易经分析主函数 + performYijingAnalysis(inputData) { + try { + const { question, user_id, birth_data } = inputData; + const currentTime = new Date(); + const hexagramData = this.generateHexagram(currentTime, user_id); + const mainHexagramInfo = this.getHexagramInfo(hexagramData.mainHex); + const changingHexagramInfo = this.getChangingHexagram(mainHexagramInfo, hexagramData.changingLines); + const changingLineAnalysis = this.analyzeChangingLines(mainHexagramInfo, hexagramData.changingLines); + + return { + analysis_type: 'yijing', + analysis_date: currentTime.toISOString().split('T')[0], + basic_info: { + divination_data: { + question: question, + divination_time: currentTime.toISOString(), + method: '梅花易数时间起卦法' + }, + hexagram_info: { + main_hexagram: mainHexagramInfo.name, + main_hexagram_symbol: mainHexagramInfo.symbol, + changing_hexagram: changingHexagramInfo ? changingHexagramInfo.name : '无', + changing_hexagram_symbol: changingHexagramInfo ? changingHexagramInfo.symbol : '无', + changing_lines: hexagramData.changingLines, + detailed_interpretation: `您得到的本卦是【${mainHexagramInfo.name}】,${mainHexagramInfo.judgment}` + } + }, + detailed_analysis: { + hexagram_analysis: { + primary_meaning: `【${mainHexagramInfo.name}卦】 - ${mainHexagramInfo.meaning}`, + judgment: `【彖传】曰:${mainHexagramInfo.judgment}`, + image: `【象传】曰:${mainHexagramInfo.image}` + }, + changing_lines_analysis: changingLineAnalysis, + changing_hexagram_analysis: changingHexagramInfo ? { + name: `变卦为【${changingHexagramInfo.name}】`, + meaning: changingHexagramInfo.meaning, + transformation_insight: `从【${mainHexagramInfo.name}】到【${changingHexagramInfo.name}】的变化,预示着:${changingHexagramInfo.transformation || '事态将发生转变'}` + } : { + name: '无变卦', + meaning: '事态稳定,应以本卦为主要参考。', + transformation_insight: '没有动爻,表示当前状况将持续一段时间,应专注于当下。' + } + }, + life_guidance: { + overall_fortune: this.generateOverallFortune(mainHexagramInfo, changingHexagramInfo, question), + career_guidance: this.generateCareerAdvice(mainHexagramInfo, changingHexagramInfo), + relationship_guidance: this.generateRelationshipAdvice(mainHexagramInfo, changingHexagramInfo) + }, + divination_wisdom: { + key_message: mainHexagramInfo.keyMessage || '保持平常心,顺势而为', + action_advice: mainHexagramInfo.actionAdvice || '谨慎行事,稳步前进', + warning: mainHexagramInfo.warning || '无特别警示', + philosophical_insight: `《易经》${mainHexagramInfo.name}卦的核心智慧在于:${mainHexagramInfo.philosophy || '顺应自然,把握时机'}` + } + }; + } catch (error) { + console.error('易经分析详细错误:', error); + throw error; + } + } + + // 生成卦象 + generateHexagram(currentTime, userId) { + const year = currentTime.getFullYear(); + const month = currentTime.getMonth() + 1; + const day = currentTime.getDate(); + const hour = currentTime.getHours(); + const minute = currentTime.getMinutes(); + + const userFactor = userId ? parseInt(String(userId).slice(-5).replace(/[^0-9]/g, '') || '12', 10) : 12; + + const upperTrigramNum = (year + month + day + userFactor) % 8 || 8; + const lowerTrigramNum = (year + month + day + hour + minute + userFactor) % 8 || 8; + const changingLinePos = (year + month + day + hour + minute + userFactor) % 6 + 1; + + const mainHexNumber = this.getHexagramNumber(upperTrigramNum, lowerTrigramNum); + + return { + mainHex: mainHexNumber, + changingLines: [changingLinePos] + }; + } + + // 获取卦象编号 + getHexagramNumber(upper, lower) { + const trigramMap = { + 1: '乾', 2: '兑', 3: '离', 4: '震', + 5: '巽', 6: '坎', 7: '艮', 8: '坤' + }; + + const upperName = trigramMap[upper]; + const lowerName = trigramMap[lower]; + + for (const hexNum in this.ALL_HEXAGRAMS) { + const hex = this.ALL_HEXAGRAMS[hexNum]; + if (hex.upperTrigram === upperName && hex.lowerTrigram === lowerName) { + return parseInt(hexNum); + } + } + return 1; + } + + // 获取卦象信息 + getHexagramInfo(hexNumber) { + return this.ALL_HEXAGRAMS[hexNumber] || this.ALL_HEXAGRAMS[1]; + } + + // 分析动爻 + analyzeChangingLines(hexagramInfo, changingLines) { + if (!changingLines || changingLines.length === 0) { + return { + method: '以本卦卦辞为断', + analysis: `无动爻,应以【${hexagramInfo.name}】的整体卦辞为主要判断依据。`, + guidance: hexagramInfo.judgment + }; + } + + const linePos = changingLines[0]; + const lineIndex = linePos - 1; + const lineData = hexagramInfo.lines[lineIndex]; + + if (!lineData) { + return { + method: '动爻分析异常', + analysis: '无法找到对应的爻辞信息。', + guidance: '' + }; + } + + const lineName = ['初', '二', '三', '四', '五', '上'][lineIndex] + (lineData.type === 'yang' ? '九' : '六'); + + return { + method: '以动爻爻辞为断', + changing_line_position: `${lineName}(第${linePos}爻)`, + line_meaning: `【爻辞】曰:${lineData.text}`, + line_image_meaning: `【象传】对该爻的解释:${lineData.image}`, + guidance: `当前阶段的关键点在于理解和应对"${lineData.text}"所揭示的情况。` + }; + } + + // 获取变卦 + getChangingHexagram(originalHexInfo, changingLines) { + if (!changingLines || changingLines.length === 0) { + return null; + } + + let originalBinary = originalHexInfo.binary; + changingLines.forEach(linePos => { + const index = 6 - linePos; + if (index >= 0 && index < 6) { + const charArray = originalBinary.split(''); + charArray[index] = charArray[index] === '1' ? '0' : '1'; + originalBinary = charArray.join(''); + } + }); + + for (const hexNum in this.ALL_HEXAGRAMS) { + if (this.ALL_HEXAGRAMS[hexNum].binary === originalBinary) { + return this.ALL_HEXAGRAMS[hexNum]; + } + } + return null; + } + + // 生成整体运势分析 + generateOverallFortune(mainHex, changeHex, question) { + let fortune = `针对您的问题"${question}",本卦为【${mainHex.name}】,指示了当前的基本状况:${mainHex.guidance}。`; + + if (changeHex) { + fortune += ` 动爻预示着变化,未来趋势可参考变卦【${changeHex.name}】:${changeHex.guidance}。`; + } else { + fortune += ` 事态稳定,应专注于当前状况。`; + } + + return fortune; + } + + // 生成事业建议 + generateCareerAdvice(mainHex, changeHex) { + let advice = `事业方面,本卦【${mainHex.name}】给出的指引是:"${mainHex.career || mainHex.guidance}"。`; + + if (changeHex) { + advice += ` 考虑到变卦为【${changeHex.name}】,未来发展可能转向"${changeHex.career || changeHex.guidance}"的方向,需提前准备。`; + } + + return advice; + } + + // 生成感情建议 + generateRelationshipAdvice(mainHex, changeHex) { + let advice = `感情方面,本卦【${mainHex.name}】的启示是:"${mainHex.relationship || mainHex.guidance}"。`; + + if (changeHex) { + advice += ` 动爻的变化指向【${changeHex.name}】卦,暗示关系可能朝"${changeHex.relationship || changeHex.guidance}"演变,请注意把握。`; + } + + return advice; + } + + // 初始化64卦数据 + initializeHexagrams() { + this.ALL_HEXAGRAMS = { + 1: { + number: 1, name: '乾', symbol: '䷀', binary: '111111', + upperTrigram: '乾', lowerTrigram: '乾', + meaning: '创造,力量,活动', + judgment: '元亨,利贞。', + image: '天行健,君子以自强不息。', + guidance: '充满创造力和能量,是采取行动和领导的绝佳时机。保持正直和坚持,将获得巨大成功。', + career: '适合开拓新项目,担任领导角色。你的权威和能力会得到认可。', + relationship: '关系中充满活力和激情。适合主动,但需避免过于强势。', + keyMessage: '自强不息', + actionAdvice: '积极行动,坚持不懈', + philosophy: '天道刚健,君子应效法天道,奋发图强。', + lines: [ + { type: 'yang', text: '潜龙勿用。', image: '阳在下也。' }, + { type: 'yang', text: '见龙在田,利见大人。', image: '德施普也。' }, + { type: 'yang', text: '君子终日乾乾,夕惕若厉,无咎。', image: '反复道也。' }, + { type: 'yang', text: '或跃在渊,无咎。', image: '进无咎也。' }, + { type: 'yang', text: '飞龙在天,利见大人。', image: '大人造也。' }, + { type: 'yang', text: '亢龙有悔。', image: '盈不可久也。' } + ] + }, + 2: { + number: 2, name: '坤', symbol: '䷁', binary: '000000', + upperTrigram: '坤', lowerTrigram: '坤', + meaning: '接受,滋养,顺从', + judgment: '元亨,利牝马之贞。', + image: '地势坤,君子以厚德载物。', + guidance: '以柔顺和包容的态度面对挑战。通过支持他人和耐心等待,将获得成功。', + career: '适合支持性角色,通过团队合作和服务他人获得成功。', + relationship: '关系中需要更多的理解和包容。适合倾听和支持伴侣。', + keyMessage: '厚德载物', + actionAdvice: '以柔克刚,包容万物', + philosophy: '地道柔顺,君子应效法大地,厚德载物。', + lines: [ + { type: 'yin', text: '履霜,坚冰至。', image: '履霜坚冰,阴始凝也。' }, + { type: 'yin', text: '直,方,大,不习无不利。', image: '六二之动,直以方也。' }, + { type: 'yin', text: '含章可贞。或从王事,无成有终。', image: '含章可贞,以时发也。' }, + { type: 'yin', text: '括囊;无咎,无誉。', image: '括囊无咎,慎不害也。' }, + { type: 'yin', text: '黄裳,元吉。', image: '黄裳元吉,文在中也。' }, + { type: 'yin', text: '龙战于野,其血玄黄。', image: '龙战于野,其道穷也。' } + ] + } + }; + + // 第3-10卦的完整数据 + this.ALL_HEXAGRAMS[3] = { + number: 3, + name: '屯', + symbol: '䷂', + binary: '010001', + upperTrigram: '坎', + lowerTrigram: '震', + meaning: '初生,困难,聚集', + judgment: '元亨,利贞。勿用有攸往,利建侯。', + image: '云雷,屯。君子以经纶。', + guidance: '开始阶段会遇到困难,像种子在发芽。需要耐心和毅力,不要急于求成,但要建立基础和秩序。', + career: '新项目或新职位初期会遇到挑战。关键是建立好框架和计划,而不是急于扩张。', + relationship: '新关系开始时可能会有不确定性。需要耐心培养,建立信任。', + keyMessage: '艰难起步', + actionAdvice: '耐心建立基础', + philosophy: '万物初生皆艰难,君子应在此时规划未来,建立秩序。', + lines: [ + { type: 'yang', text: '磐桓,利居贞,利建侯。', image: '以贵下贱,大得民也。' }, + { type: 'yin', text: '屯如邅如,乘马班如。匪寇婚媾,女子贞不字,十年乃字。', image: '六二之难,乘刚也。' }, + { type: 'yin', text: '即鹿无虞,惟入于林中。君子几,不如舍,往吝。', image: '即鹿无虞,以从禽也。' }, + { type: 'yin', text: '乘马班如,求婚媾,往吉,无不利。', image: '求而往,明也。' }, + { type: 'yang', text: '屯其膏,小贞吉,大贞凶。', image: '屯其膏,施未光也。' }, + { type: 'yin', text: '乘马班如,泣血涟如。', image: '何可长也。' } + ] + }; + + this.ALL_HEXAGRAMS[4] = { + number: 4, + name: '蒙', + symbol: '䷃', + binary: '100010', + upperTrigram: '艮', + lowerTrigram: '坎', + meaning: '启蒙,无知,教育', + judgment: '亨。匪我求童蒙,童蒙求我。初筮告,再三渎,渎则不告。利贞。', + image: '山下出泉,蒙。君子以果行育德。', + guidance: '处于学习和启蒙阶段。保持谦逊和开放的心态,寻求有智慧的指导。不要因为困惑而停止前进。', + career: '适合学习新技能或进入新领域。寻找一位导师或榜样对你非常有益。', + relationship: '关系中可能存在误解或不成熟。需要清晰、真诚的沟通来消除困惑。', + keyMessage: '启蒙育德', + actionAdvice: '虚心求教,果断行动', + philosophy: '山下出泉,水流不定,象征蒙昧。君子应以果敢的行动来培养品德。', + lines: [ + { type: 'yin', text: '发蒙,利用刑人,用说桎梏,以往吝。', image: '利用刑人,以正法也。' }, + { type: 'yang', text: '包蒙,吉。纳妇,吉。子克家。', image: '子克家,刚柔接也。' }, + { type: 'yin', text: '勿用取女,见金夫,不有躬,无攸利。', image: '勿用取女,行不顺也。' }, + { type: 'yin', text: '困蒙,吝。', image: '困蒙之吝,独远实也。' }, + { type: 'yin', text: '童蒙,吉。', image: '童蒙之吉,顺以巽也。' }, + { type: 'yang', text: '击蒙,不利为寇,利御寇。', image: '利御寇,上下顺也。' } + ] + }; + + this.ALL_HEXAGRAMS[5] = { + number: 5, + name: '需', + symbol: '䷄', + binary: '111010', + upperTrigram: '坎', + lowerTrigram: '乾', + meaning: '等待,滋养,需求', + judgment: '有孚,光亨,贞吉。利涉大川。', + image: '云上于天,需。君子以饮食宴乐。', + guidance: '现在是耐心等待的时机。时机尚未成熟,急于行动会导致失败。利用这段时间积蓄力量,做好准备。', + career: '项目进展可能会延迟。不要强行推进,利用时间完善计划和策略。', + relationship: '关系发展需要耐心。给彼此一些时间和空间,等待合适的时机。', + keyMessage: '耐心等待', + actionAdvice: '积蓄力量,待机而动', + philosophy: '云在天上,雨水尚未降下,象征等待。君子应在此期间休养生息,享受生活。', + lines: [ + { type: 'yang', text: '需于郊,利用恒,无咎。', image: '需于郊,不犯难行也。' }, + { type: 'yang', text: '需于沙,小有言,终吉。', image: '需于沙,衍在中也。' }, + { type: 'yang', text: '需于泥,致寇至。', image: '需于泥,灾在外也。' }, + { type: 'yin', text: '需于血,出自穴。', image: '需于血,顺以听也。' }, + { type: 'yang', text: '需于酒食,贞吉。', image: '酒食贞吉,以中正也。' }, + { type: 'yin', text: '入于穴,有不速之客三人来,敬之终吉。', image: '不速之客来,敬之终吉。虽不当位,未大失也。' } + ] + }; + + this.ALL_HEXAGRAMS[6] = { + number: 6, + name: '讼', + symbol: '䷅', + binary: '010111', + upperTrigram: '乾', + lowerTrigram: '坎', + meaning: '冲突,争论,诉讼', + judgment: '有孚,窒惕,中吉。终凶。利见大人,不利涉大川。', + image: '天与水违行,讼。君子以作事谋始。', + guidance: '可能会遇到冲突和争论。保持冷静和公正,寻求调解是明智的。将冲突升级会导致不良后果。', + career: '工作中可能出现分歧或法律纠纷。避免正面冲突,寻求第三方仲裁或和解。', + relationship: '关系中可能出现争吵。关键是沟通和理解,而不是争论谁对谁错。', + keyMessage: '止息争讼', + actionAdvice: '寻求和解,避免冲突', + philosophy: '天水相违,象征争讼。君子在做事之初就应谋划周全,以避免未来的争端。', + lines: [ + { type: 'yin', text: '不永所事,小有言,终吉。', image: '不永所事,讼不可长也。' }, + { type: 'yang', text: '不克讼,归而逋,其邑人三百户,无眚。', image: '不克讼,归逋窜也。' }, + { type: 'yin', text: '食旧德,贞厉,终吉。或从王事,无成。', image: '食旧德,从上吉也。' }, + { type: 'yang', text: '不克讼,复即命,渝安贞,吉。', image: '复即命,渝安贞,不失也。' }, + { type: 'yang', text: '讼,元吉。', image: '讼元吉,以中正也。' }, + { type: 'yang', text: '或锡之鞶带,终朝三褫之。', image: '以讼受服,亦不足敬也。' } + ] + }; + + this.ALL_HEXAGRAMS[7] = { + number: 7, + name: '师', + symbol: '䷆', + binary: '000010', + upperTrigram: '坤', + lowerTrigram: '坎', + meaning: '军队,纪律,组织', + judgment: '贞,丈人吉,无咎。', + image: '地中有水,师。君子以容民畜众。', + guidance: '需要纪律、组织和领导力。像将军一样,你需要一个明确的目标和一群忠诚的追随者。', + career: '适合领导团队或管理大型项目。需要建立清晰的规则和强大的组织能力。', + relationship: '家庭或团队中需要建立秩序和规则。明确的角色和责任有助于和谐。', + keyMessage: '师出有名', + actionAdvice: '建立纪律,统一行动', + philosophy: '地下藏水,象征军队。君子应效法大地容纳江河,包容民众,蓄养力量。', + lines: [ + { type: 'yin', text: '师出以律,否臧凶。', image: '师出以律,失次凶也。' }, + { type: 'yang', text: '在师中,吉,无咎,王三锡命。', image: '在师中吉,承天宠也。' }, + { type: 'yin', text: '师或舆尸,凶。', image: '师或舆尸,大无功也。' }, + { type: 'yin', text: '师左次,无咎。', image: '左次无咎,未失常也。' }, + { type: 'yin', text: '田有禽,利执言,无咎。长子帅师,弟子舆尸,贞凶。', image: '长子帅师,以中行也。' }, + { type: 'yin', text: '大君有命,开国承家,小人勿用。', image: '大君有命,以正功也。' } + ] + }; + + this.ALL_HEXAGRAMS[8] = { + number: 8, + name: '比', + symbol: '䷇', + binary: '010000', + upperTrigram: '坎', + lowerTrigram: '坤', + meaning: '联合,亲近,和谐', + judgment: '吉。原筮,元永贞,无咎。不宁方来,后夫凶。', + image: '地上有水,比。先王以建万国,亲诸侯。', + guidance: '这是联合和亲近的时期。寻求与他人建立和谐的关系,找到志同道合的伙伴。', + career: '团队合作和建立良好的人际网络是成功的关键。', + relationship: '加强与伴侣、家人和朋友的联系。这是建立更深层次情感纽带的好时机。', + keyMessage: '亲密无间', + actionAdvice: '加强联合,促进和谐', + philosophy: '水在地上,相亲相辅,象征联合。古代君王因此分封万国,亲近诸侯。', + lines: [ + { type: 'yin', text: '有孚比之,无咎。有孚盈缶,终来有他吉。', image: '比之初六,有他吉也。' }, + { type: 'yin', text: '比之自内,贞吉。', image: '比之自内,不自失也。' }, + { type: 'yin', text: '比之匪人。', image: '比之匪人,不亦伤乎!' }, + { type: 'yin', text: '外比之,贞吉。', image: '外比于贤,以从上也。' }, + { type: 'yang', text: '显比,王用三驱,失前禽。邑人不诫,吉。', image: '显比之吉,位正中也。' }, + { type: 'yin', text: '比之无首,凶。', image: '比之无首,无所终也。' } + ] + }; + + this.ALL_HEXAGRAMS[9] = { + number: 9, + name: '小畜', + symbol: '䷈', + binary: '110111', + upperTrigram: '巽', + lowerTrigram: '乾', + meaning: '小的积蓄,节制,养育', + judgment: '亨。密云不雨,自我西郊。', + image: '风行天上,小畜。君子以懿文德。', + guidance: '现在是积蓄力量、培养品德的时期。虽然有大志,但时机未到,需要耐心等待和积累。', + career: '适合积累经验、学习技能,为将来的大发展做准备。不要急于求成。', + relationship: '关系需要慢慢培养,不要急于推进。通过小事情积累感情。', + keyMessage: '积小成大', + actionAdvice: '积蓄力量,等待时机', + philosophy: '风在天上吹,象征小的积蓄。君子应在此期间修养文德,完善自我。', + lines: [ + { type: 'yang', text: '复自道,何其咎?吉。', image: '复自道,其义吉也。' }, + { type: 'yang', text: '牵复,吉。', image: '牵复在中,亦不自失也。' }, + { type: 'yang', text: '舆说辐,夫妻反目。', image: '夫妻反目,不能正室也。' }, + { type: 'yin', text: '有孚,血去惕出,无咎。', image: '有孚惕出,上合志也。' }, + { type: 'yang', text: '有孚挛如,富以其邻。', image: '有孚挛如,不独富也。' }, + { type: 'yang', text: '既雨既处,尚德载,妇贞厉。月几望,君子征凶。', image: '既雨既处,德积载也。君子征凶,有所疑也。' } + ] + }; + + this.ALL_HEXAGRAMS[10] = { + number: 10, + name: '履', + symbol: '䷉', + binary: '111011', + upperTrigram: '乾', + lowerTrigram: '兑', + meaning: '履行,实践,礼仪', + judgment: '履虎尾,不咥人,亨。', + image: '上天下泽,履。君子以辩上下,定民志。', + guidance: '需要谨慎行事,像在老虎尾巴后面行走一样。遵循正确的礼仪和规范,可以避免危险。', + career: '工作中需要谨慎行事,遵守规则和程序。不要冒险,按部就班地推进。', + relationship: '交往中要尊重对方,遵循适当的礼仪。避免冒犯,保持礼貌。', + keyMessage: '谨慎行事', + actionAdvice: '遵循规范,避免冒险', + philosophy: '天在上,泽在下,上下分明。君子应分辨上下,安定民心。', + lines: [ + { type: 'yang', text: '素履,往无咎。', image: '素履之往,独行愿也。' }, + { type: 'yang', text: '履道坦坦,幽人贞吉。', image: '幽人贞吉,中不自乱也。' }, + { type: 'yin', text: '眇能视,跛能履,履虎尾咥人,凶。武人为于大君。', image: '眇能视,不足以有明也。跛能履,不足以与行也。咥人之凶,位不当也。武人为于大君,志刚也。' }, + { type: 'yang', text: '履虎尾,愬愬终吉。', image: '愬愬终吉,志行也。' }, + { type: 'yang', text: '夬履,贞厉。', image: '夬履贞厉,位正当也。' }, + { type: 'yang', text: '视履考祥,其旋元吉。', image: '元吉在上,大有庆也。' } + ] + }; + + // 第11-20卦的完整数据 + this.ALL_HEXAGRAMS[11] = { + number: 11, + name: '泰', + symbol: '䷊', + binary: '000111', + upperTrigram: '坤', + lowerTrigram: '乾', + meaning: '通达,和谐,安泰', + judgment: '小往大来,吉,亨。', + image: '天地交,泰。后以财成天地之道,辅相天地之宜,以左右民。', + guidance: '天地交泰,万物和谐。这是一个吉祥的时期,适合开展各种活动,将获得丰厚的回报。', + career: '事业顺利,合作愉快。适合扩大规模,开拓新领域。', + relationship: '关系和谐美满,沟通顺畅。是深化感情的好时机。', + keyMessage: '天地交泰', + actionAdvice: '顺势而为,积极发展', + philosophy: '天地交感,万物通泰。君主应顺应天地之道,辅助教化民众。', + lines: [ + { type: 'yang', text: '拔茅茹,以其汇,征吉。', image: '拔茅征吉,志在外也。' }, + { type: 'yang', text: '包荒,用冯河,不遐遗,朋亡,得尚于中行。', image: '包荒,得尚于中行,以光大也。' }, + { type: 'yang', text: '无平不陂,无往不复,艰贞无咎。勿恤其孚,于食有福。', image: '无往不复,天地际也。' }, + { type: 'yin', text: '翩翩不富以其邻,不戒以孚。', image: '翩翩不富,皆失实也。不戒以孚,中心愿也。' }, + { type: 'yin', text: '帝乙归妹,以祉元吉。', image: '以祉元吉,中以行愿也。' }, + { type: 'yin', text: '城复于隍,勿用师。自邑告命,贞吝。', image: '城复于隍,其命乱也。' } + ] + }; + + this.ALL_HEXAGRAMS[12] = { + number: 12, + name: '否', + symbol: '䷋', + binary: '111000', + upperTrigram: '乾', + lowerTrigram: '坤', + meaning: '阻塞,不通,黑暗', + judgment: '否之匪人,不利君子贞,大往小来。', + image: '天地不交,否。君子以俭德辟难,不可荣以禄。', + guidance: '天地不交,万物不通。这是一个困难的时期,需要保持低调,避免大的行动。', + career: '事业发展受阻,不宜扩张。应保守经营,等待时机好转。', + relationship: '关系出现隔阂,沟通不畅。需要耐心和理解,避免冲突。', + keyMessage: '韬光养晦', + actionAdvice: '保守行事,等待转机', + philosophy: '天地不交,象征闭塞。君子应节俭修德,避免灾难,不贪求禄位。', + lines: [ + { type: 'yin', text: '拔茅茹,以其汇,贞吉,亨。', image: '拔茅贞吉,志在君也。' }, + { type: 'yin', text: '包承,小人吉,大人否,亨。', image: '大人否亨,不乱群也。' }, + { type: 'yin', text: '包羞。', image: '包羞,位不当也。' }, + { type: 'yang', text: '有命无咎,畴离祉。', image: '有命无咎,志行也。' }, + { type: 'yang', text: '休否,大人吉。其亡其亡,系于苞桑。', image: '大人之吉,位正当也。' }, + { type: 'yang', text: '倾否,先否后喜。', image: '否终则倾,何可长也。' } + ] + }; + + this.ALL_HEXAGRAMS[13] = { + number: 13, + name: '同人', + symbol: '䷌', + binary: '101111', + upperTrigram: '乾', + lowerTrigram: '离', + meaning: '同人,团结,和谐', + judgment: '同人于野,亨。利涉大川,利君子贞。', + image: '天与火,同人。君子以类族辨物。', + guidance: '这是团结合作的时期。与志同道合的人联合,可以克服困难,实现远大目标。', + career: '团队合作将带来成功。寻找有共同目标的伙伴,共同努力。', + relationship: '与伴侣或朋友同心协力,关系将更加牢固和谐。', + keyMessage: '同心协力', + actionAdvice: '寻求合作,团结一致', + philosophy: '天与火同升,象征同心同德。君子应分类辨物,团结众人。', + lines: [ + { type: 'yang', text: '同人于门,无咎。', image: '出门同人,又谁咎也。' }, + { type: 'yin', text: '同人于宗,吝。', image: '同人于宗,吝道也。' }, + { type: 'yang', text: '伏戎于莽,升其高陵,三岁不兴。', image: '伏戎于莽,敌刚也。三岁不兴,安行也。' }, + { type: 'yang', text: '乘其墉,弗克攻,吉。', image: '乘其墉,义弗克也。其吉,则困而反则也。' }, + { type: 'yang', text: '同人,先号咷而后笑,大师克相遇。', image: '同人之先,以中直也。大师相遇,言相克也。' }, + { type: 'yang', text: '同人于郊,无悔。', image: '同人于郊,志未得也。' } + ] + }; + + this.ALL_HEXAGRAMS[14] = { + number: 14, + name: '大有', + symbol: '䷍', + binary: '111101', + upperTrigram: '离', + lowerTrigram: '乾', + meaning: '大有,丰盛,富有', + judgment: '元亨。', + image: '火在天上,大有。君子以遏恶扬善,顺天休命。', + guidance: '这是一个丰盛富足的时期。拥有很多资源和机会,应当善用这些优势,同时保持谦逊。', + career: '事业兴旺,资源丰富。适合扩大规模,但要避免骄傲自满。', + relationship: '关系充实美满,感情丰富。要珍惜现有的幸福,避免过度索取。', + keyMessage: '丰盛富足', + actionAdvice: '善用资源,保持谦逊', + philosophy: '火在天上,光明普照,象征大有。君子应抑恶扬善,顺应天命。', + lines: [ + { type: 'yang', text: '无交害,匪咎,艰则无咎。', image: '大有初九,无交害也。' }, + { type: 'yang', text: '大车以载,有攸往,无咎。', image: '大车以载,积中不败也。' }, + { type: 'yang', text: '公用亨于天子,小人弗克。', image: '公用亨于天子,小人害也。' }, + { type: 'yang', text: '匪其彭,无咎。', image: '匪其彭,无咎,明辨皙也。' }, + { type: 'yin', text: '厥孚交如,威如,吉。', image: '厥孚交如,信以发志也。威如之吉,易而无备也。' }, + { type: 'yang', text: '自天佑之,吉无不利。', image: '大有上吉,自天佑也。' } + ] + }; + + this.ALL_HEXAGRAMS[15] = { + number: 15, + name: '谦', + symbol: '䷎', + binary: '001000', + upperTrigram: '坤', + lowerTrigram: '艮', + meaning: '谦虚,谦逊,低调', + judgment: '亨,君子有终。', + image: '地中有山,谦。君子以裒多益寡,称物平施。', + guidance: '保持谦虚和低调。真正的力量在于内在的谦逊,这将带来长久的成功和尊重。', + career: '即使取得成就也要保持谦逊。谦虚的态度会赢得更多支持和机会。', + relationship: '在关系中保持谦逊,不炫耀,不争强好胜,会让感情更加和谐。', + keyMessage: '谦虚受益', + actionAdvice: '保持谦逊,低调行事', + philosophy: '山在地中,象征谦虚。君子应减少过多,补充不足,公平施予。', + lines: [ + { type: 'yin', text: '谦谦君子,用涉大川,吉。', image: '谦谦君子,卑以自牧也。' }, + { type: 'yin', text: '鸣谦,贞吉。', image: '鸣谦贞吉,中心得也。' }, + { type: 'yang', text: '劳谦,君子有终,吉。', image: '劳谦君子,万民服也。' }, + { type: 'yin', text: '无不利,撝谦。', image: '无不利撝谦,不违则也。' }, + { type: 'yin', text: '不富以其邻,利用侵伐,无不利。', image: '利用侵伐,征不服也。' }, + { type: 'yin', text: '鸣谦,利用行师,征邑国。', image: '鸣谦,志未得也。可用行师,征邑国也。' } + ] + }; + + this.ALL_HEXAGRAMS[16] = { + number: 16, + name: '豫', + symbol: '䷏', + binary: '000100', + upperTrigram: '震', + lowerTrigram: '坤', + meaning: '喜悦,预备,娱乐', + judgment: '利建侯行师。', + image: '雷出地奋,豫。先王以作乐崇德,殷荐之上帝,以配祖考。', + guidance: '这是一个喜悦和预备的时期。保持乐观的心态,为未来的行动做好准备。', + career: '适合制定计划,为未来的发展做准备。保持积极的态度。', + relationship: '享受当下的美好时光,同时为关系的进一步发展做准备。', + keyMessage: '喜悦预备', + actionAdvice: '保持乐观,做好准备', + philosophy: '雷出地上,万物振奋,象征喜悦。古代君王因此制礼作乐,推崇德行。', + lines: [ + { type: 'yin', text: '鸣豫,凶。', image: '初六鸣豫,志穷凶也。' }, + { type: 'yin', text: '介于石,不终日,贞吉。', image: '不终日贞吉,以中正也。' }, + { type: 'yin', text: '盱豫悔,迟有悔。', image: '盱豫有悔,位不当也。' }, + { type: 'yang', text: '由豫,大有得,勿疑朋盍簪。', image: '由豫大有得,志大行也。' }, + { type: 'yin', text: '贞疾,恒不死。', image: '六五贞疾,乘刚也。恒不死,中未亡也。' }, + { type: 'yin', text: '冥豫,成有渝,无咎。', image: '冥豫在上,何可长也。' } + ] + }; + + this.ALL_HEXAGRAMS[17] = { + number: 17, + name: '随', + symbol: '䷐', + binary: '100110', + upperTrigram: '兑', + lowerTrigram: '震', + meaning: '跟随,顺应,随和', + judgment: '元亨,利贞,无咎。', + image: '泽中有雷,随。君子以向晦入宴息。', + guidance: '这是顺应时势、跟随领导的时期。保持灵活和适应性,将获得顺利的发展。', + career: '适合跟随有经验的领导或团队,学习经验,不要急于出头。', + relationship: '在关系中保持随和,跟随对方的节奏,会让关系更加和谐。', + keyMessage: '顺势而为', + actionAdvice: '保持灵活,跟随领导', + philosophy: '雷在泽中,象征跟随。君子应顺应天时,该休息时休息。', + lines: [ + { type: 'yang', text: '官有渝,贞吉。出门交有功。', image: '官有渝,从正吉也。出门交有功,不失也。' }, + { type: 'yin', text: '系小子,失丈夫。', image: '系小子,弗兼与也。' }, + { type: 'yin', text: '系丈夫,失小子。随有求得,利居贞。', image: '系丈夫,志舍下也。' }, + { type: 'yang', text: '随有获,贞凶。有孚在道,以明,何咎。', image: '随有获,其义凶也。有孚在道,明功也。' }, + { type: 'yang', text: '孚于嘉,吉。', image: '孚于嘉吉,位正中也。' }, + { type: 'yin', text: '拘系之,乃从维之。王用亨于西山。', image: '拘系之,上穷也。' } + ] + }; + + this.ALL_HEXAGRAMS[18] = { + number: 18, + name: '蛊', + symbol: '䷑', + binary: '011001', + upperTrigram: '艮', + lowerTrigram: '巽', + meaning: '腐败,革新,整治', + judgment: '元亨,利涉大川。先甲三日,后甲三日。', + image: '山下有风,蛊。君子以振民育德。', + guidance: '需要革新和整治腐败。虽然面临困难,但这是必要的改革,将带来新的生机。', + career: '需要改革或整顿工作环境。虽然过程艰难,但会带来更好的发展。', + relationship: '关系中需要清理积弊,重新开始。需要勇气和决心。', + keyMessage: '革新整治', + actionAdvice: '勇于改革,清理积弊', + philosophy: '山下有风,象征腐败。君子应振奋民心,培育德行。', + lines: [ + { type: 'yin', text: '干父之蛊,有子,考无咎,厉终吉。', image: '干父之蛊,意承考也。' }, + { type: 'yang', text: '干母之蛊,不可贞。', image: '干母之蛊,得中道也。' }, + { type: 'yin', text: '干父之蛊,小有悔,无大咎。', image: '干父之蛊,终无咎也。' }, + { type: 'yin', text: '裕父之蛊,往见吝。', image: '裕父之蛊,往未得也。' }, + { type: 'yin', text: '干父之蛊,用誉。', image: '干父用誉,承以德也。' }, + { type: 'yang', text: '不事王侯,高尚其事。', image: '不事王侯,志可则也。' } + ] + }; + + this.ALL_HEXAGRAMS[19] = { + number: 19, + name: '临', + symbol: '䷒', + binary: '110000', + upperTrigram: '坤', + lowerTrigram: '兑', + meaning: '临近,监督,领导', + judgment: '元亨,利贞。至于八月有凶。', + image: '泽上有地,临。君子以教思无穷,容保民无疆。', + guidance: '处于领导或监督的位置。需要以开放的心态接近他人,给予指导和保护。', + career: '适合担任领导或管理角色。需要亲近团队,给予支持和指导。', + relationship: '在关系中扮演保护者和指导者的角色,给予对方安全感和支持。', + keyMessage: '亲近指导', + actionAdvice: '亲近他人,给予指导', + philosophy: '地在泽上,象征临近。君子应教化无穷,包容保护民众。', + lines: [ + { type: 'yin', text: '咸临,贞吉。', image: '咸临贞吉,志行正也。' }, + { type: 'yin', text: '咸临,吉无不利。', image: '咸临吉无不利,未顺命也。' }, + { type: 'yin', text: '甘临,无攸利。既忧之,无咎。', image: '甘临,位不当也。既忧之,咎不长也。' }, + { type: 'yin', text: '至临,无咎。', image: '至临无咎,位当也。' }, + { type: 'yang', text: '知临,大君之宜,吉。', image: '大君之宜,行中之谓也。' }, + { type: 'yin', text: '敦临,吉无咎。', image: '敦临之吉,志在内也。' } + ] + }; + + this.ALL_HEXAGRAMS[20] = { + number: 20, + name: '观', + symbol: '䷓', + binary: '000011', + upperTrigram: '巽', + lowerTrigram: '坤', + meaning: '观察,展示,省察', + judgment: '盥而不荐,有孚颙若。', + image: '风行地上,观。先王以省方观民设教。', + guidance: '需要仔细观察和审视。像祭祀前洗手一样,保持虔诚和专注的态度。', + career: '适合观察市场动向,学习他人经验。不要急于行动,先了解情况。', + relationship: '需要更多观察和理解对方。保持耐心和关注。', + keyMessage: '仔细观察', + actionAdvice: '保持专注,深入了解', + philosophy: '风行地上,遍观万物。古代君王因此巡视四方,观察民情,设立教化。', + lines: [ + { type: 'yin', text: '童观,小人无咎,君子吝。', image: '初六童观,小人道也。' }, + { type: 'yin', text: '窥观,利女贞。', image: '窥观女贞,亦可丑也。' }, + { type: 'yin', text: '观我生,进退。', image: '观我生,进退未失道也。' }, + { type: 'yin', text: '观国之光,利用宾于王。', image: '观国之光,尚宾也。' }, + { type: 'yang', text: '观我生,君子无咎。', image: '观我生,观民也。' }, + { type: 'yang', text: '观其生,君子无咎。', image: '观其生,志未平也。' } + ] + }; + + // 第21-30卦的完整数据 + this.ALL_HEXAGRAMS[21] = { + number: 21, + name: '噬嗑', + symbol: '䷔', + binary: '100101', + upperTrigram: '离', + lowerTrigram: '震', + meaning: '咬合,刑罚,决断', + judgment: '亨。利用狱。', + image: '雷电,噬嗑。先王以明罚敕法。', + guidance: '需要果断处理障碍和问题。像咬合一样,清除阻碍,恢复通畅。', + career: '需要解决工作中的障碍或冲突。采取果断措施,清除问题。', + relationship: '关系中需要解决积累的矛盾。坦诚沟通,消除误会。', + keyMessage: '果断处理', + actionAdvice: '清除障碍,解决问题', + philosophy: '雷电交加,象征咬合。古代君王因此明正刑罚,整饬法度。', + lines: [ + { type: 'yin', text: '屦校灭趾,无咎。', image: '屦校灭趾,不行也。' }, + { type: 'yin', text: '噬肤灭鼻,无咎。', image: '噬肤灭鼻,乘刚也。' }, + { type: 'yin', text: '噬腊肉,遇毒,小吝,无咎。', image: '遇毒,位不当也。' }, + { type: 'yang', text: '噬干胏,得金矢,利艰贞,吉。', image: '利艰贞吉,未光也。' }, + { type: 'yin', text: '噬干肉,得黄金,贞厉,无咎。', image: '贞厉无咎,得当也。' }, + { type: 'yang', text: '何校灭耳,凶。', image: '何校灭耳,聪不明也。' } + ] + }; + + this.ALL_HEXAGRAMS[22] = { + number: 22, + name: '贲', + symbol: '䷕', + binary: '101001', + upperTrigram: '艮', + lowerTrigram: '离', + meaning: '装饰,美化,文饰', + judgment: '亨。小利有攸往。', + image: '山下有火,贲。君子以明庶政,无敢折狱。', + guidance: '需要适当的装饰和美化。注重外表和形式,但不要忽视内在的本质。', + career: '适合改善形象,提升外在表现。但要注意实质内容的提升。', + relationship: '关系中需要一些浪漫和美化,让感情更加丰富多彩。', + keyMessage: '适度装饰', + actionAdvice: '美化外表,注重实质', + philosophy: '山下有火,光辉照耀,象征装饰。君子应明察政务,不轻率断案。', + lines: [ + { type: 'yang', text: '贲其趾,舍车而徒。', image: '舍车而徒,义弗乘也。' }, + { type: 'yin', text: '贲其须。', image: '贲其须,与上兴也。' }, + { type: 'yang', text: '贲如濡如,永贞吉。', image: '永贞之吉,终莫之陵也。' }, + { type: 'yin', text: '贲如皤如,白马翰如,匪寇婚媾。', image: '六四当位疑也。匪寇婚媾,终无尤也。' }, + { type: 'yin', text: '贲于丘园,束帛戋戋,吝,终吉。', image: '六五之吉,有喜也。' }, + { type: 'yang', text: '白贲,无咎。', image: '白贲无咎,上得志也。' } + ] + }; + + this.ALL_HEXAGRAMS[23] = { + number: 23, + name: '剥', + symbol: '䷖', + binary: '000001', + upperTrigram: '艮', + lowerTrigram: '坤', + meaning: '剥落,侵蚀,衰败', + judgment: '不利有攸往。', + image: '山附于地,剥。上以厚下安宅。', + guidance: '处于衰败和剥落的时期。需要接受现实,保存实力,等待时机好转。', + career: '事业可能面临衰退或困难。需要保守经营,减少损失。', + relationship: '关系可能出现裂痕或疏远。需要耐心修复,或接受现实。', + keyMessage: '保存实力', + actionAdvice: '接受现实,等待转机', + philosophy: '山附于地,象征剥落。上位者应厚待下民,安定居所。', + lines: [ + { type: 'yin', text: '剥床以足,蔑贞凶。', image: '剥床以足,以灭下也。' }, + { type: 'yin', text: '剥床以辨,蔑贞凶。', image: '剥床以辨,未有与也。' }, + { type: 'yin', text: '剥之,无咎。', image: '剥之无咎,失上下也。' }, + { type: 'yin', text: '剥床以肤,凶。', image: '剥床以肤,切近灾也。' }, + { type: 'yin', text: '贯鱼,以宫人宠,无不利。', image: '以宫人宠,终无尤也。' }, + { type: 'yang', text: '硕果不食,君子得舆,小人剥庐。', image: '君子得舆,民所载也。小人剥庐,终不可用也。' } + ] + }; + + this.ALL_HEXAGRAMS[24] = { + number: 24, + name: '复', + symbol: '䷗', + binary: '100000', + upperTrigram: '坤', + lowerTrigram: '震', + meaning: '回复,复兴,回归', + judgment: '亨。出入无疾,朋来无咎。反复其道,七日来复,利有攸往。', + image: '雷在地中,复。先王以至日闭关,商旅不行,后不省方。', + guidance: '这是复兴和回归的时期。像冬至后阳气回复一样,新的开始即将到来。', + career: '事业将迎来转机,新的开始。保持信心,准备重新出发。', + relationship: '关系将得到改善或重新开始。给彼此一个新的机会。', + keyMessage: '复兴回归', + actionAdvice: '准备新生,重新开始', + philosophy: '雷在地中,象征阳气回复。古代君王在冬至闭关静养,顺应天时。', + lines: [ + { type: 'yin', text: '不远复,无祗悔,元吉。', image: '不远之复,以修身也。' }, + { type: 'yin', text: '休复,吉。', image: '休复之吉,以下仁也。' }, + { type: 'yin', text: '频复,厉无咎。', image: '频复之厉,义无咎也。' }, + { type: 'yin', text: '中行独复。', image: '中行独复,以从道也。' }, + { type: 'yin', text: '敦复,无悔。', image: '敦复无悔,中以自考也。' }, + { type: 'yin', text: '迷复,凶,有灾眚。用行师,终有大败,以其国君,凶,至于十年不克征。', image: '迷复之凶,反君道也。' } + ] + }; + + this.ALL_HEXAGRAMS[25] = { + number: 25, + name: '无妄', + symbol: '䷘', + binary: '100111', + upperTrigram: '乾', + lowerTrigram: '震', + meaning: '无妄,真诚,不妄为', + judgment: '元亨,利贞。其匪正有眚,不利有攸往。', + image: '天下雷行,物与无妄。先王以茂对时育万物。', + guidance: '保持真诚和正直,不要轻举妄动。顺应自然规律,将获得顺利发展。', + career: '工作中保持诚实正直,不要投机取巧。踏实做事会带来成功。', + relationship: '在关系中保持真诚,不要虚伪做作。真诚是感情的基础。', + keyMessage: '真诚无妄', + actionAdvice: '保持真诚,顺应自然', + philosophy: '天下雷行,万物各正其命。古代君王因此顺应天时,养育万物。', + lines: [ + { type: 'yang', text: '无妄,往吉。', image: '无妄之往,得志也。' }, + { type: 'yin', text: '不耕获,不菑畲,则利有攸往。', image: '不耕获,未富也。' }, + { type: 'yin', text: '无妄之灾,或系之牛,行人之得,邑人之灾。', image: '行人得牛,邑人灾也。' }, + { type: 'yang', text: '可贞,无咎。', image: '可贞无咎,固有之也。' }, + { type: 'yang', text: '无妄之疾,勿药有喜。', image: '无妄之药,不可试也。' }, + { type: 'yin', text: '无妄,行有眚,无攸利。', image: '无妄之行,穷之灾也。' } + ] + }; + + this.ALL_HEXAGRAMS[26] = { + number: 26, + name: '大畜', + symbol: '䷙', + binary: '111001', + upperTrigram: '艮', + lowerTrigram: '乾', + meaning: '大畜,积蓄,停止', + judgment: '利贞。不家食吉。利涉大川。', + image: '天在山中,大畜。君子以多识前言往行,以畜其德。', + guidance: '这是一个大量积蓄和准备的时期。积累知识、经验和资源,为将来大发展做准备。', + career: '适合大量学习和积累经验,为将来的事业腾飞做准备。', + relationship: '关系中需要积蓄感情和信任,为长远发展打下基础。', + keyMessage: '大量积蓄', + actionAdvice: '积累知识,准备未来', + philosophy: '天在山中,象征大量积蓄。君子应多学先贤言行,积蓄德行。', + lines: [ + { type: 'yang', text: '有厉,利已。', image: '有厉利已,不犯灾也。' }, + { type: 'yang', text: '舆说輹。', image: '舆说輹,中无尤也。' }, + { type: 'yang', text: '良马逐,利艰贞。曰闲舆卫,利有攸往。', image: '利有攸往,上合志也。' }, + { type: 'yin', text: '童牛之牿,元吉。', image: '六四元吉,有喜也。' }, + { type: 'yin', text: '豮豕之牙,吉。', image: '六五之吉,有庆也。' }, + { type: 'yang', text: '何天之衢,亨。', image: '何天之衢,道大行也。' } + ] + }; + + this.ALL_HEXAGRAMS[27] = { + number: 27, + name: '颐', + symbol: '䷚', + binary: '100001', + upperTrigram: '艮', + lowerTrigram: '震', + meaning: '颐养,饮食,养生', + judgment: '贞吉。观颐,自求口实。', + image: '山下有雷,颐。君子以慎言语,节饮食。', + guidance: '注重养生和内在调养。注意言行举止,合理饮食,保持身心健康。', + career: '工作中要注意劳逸结合,保养身体,才能持续发展。', + relationship: '关系中需要相互照顾和滋养,建立健康的关系模式。', + keyMessage: '颐养身心', + actionAdvice: '注意养生,保持健康', + philosophy: '山下有雷,象征颐养。君子应谨慎言语,节制饮食。', + lines: [ + { type: 'yang', text: '舍尔灵龟,观我朵颐,凶。', image: '观我朵颐,亦不足贵也。' }, + { type: 'yin', text: '颠颐,拂经,于丘颐,征凶。', image: '六二征凶,行失类也。' }, + { type: 'yin', text: '拂颐,贞凶,十年勿用,无攸利。', image: '十年勿用,道大悖也。' }, + { type: 'yin', text: '颠颐,吉。虎视眈眈,其欲逐逐,无咎。', image: '颠颐之吉,上施光也。' }, + { type: 'yin', text: '拂经,居贞吉,不可涉大川。', image: '居贞之吉,顺以从上也。' }, + { type: 'yang', text: '由颐,厉吉,利涉大川。', image: '由颐厉吉,大有庆也。' } + ] + }; + + // 第28-40卦的完整数据 + this.ALL_HEXAGRAMS[28] = { + number: 28, + name: '大过', + symbol: '䷛', + binary: '011110', + upperTrigram: '兑', + lowerTrigram: '巽', + meaning: '大过,过度,极端', + judgment: '栋桡,利有攸往,亨。', + image: '泽灭木,大过。君子以独立不惧,遯世无闷。', + guidance: '处于非常时期,需要采取非常措施。虽然过度,但这是必要的。', + career: '面临重大挑战,需要采取非常手段。要有勇气和决心。', + relationship: '关系中可能出现极端情况,需要非常处理。', + keyMessage: '非常时期', + actionAdvice: '采取非常措施,勇敢面对', + philosophy: '泽水淹没树木,象征过度。君子应独立不惧,避世不闷。', + lines: [ + { type: 'yin', text: '藉用白茅,无咎。', image: '藉用白茅,柔在下也。' }, + { type: 'yang', text: '枯杨生稊,老夫得其女妻,无不利。', image: '老夫女妻,过以相与也。' }, + { type: 'yang', text: '栋桡,凶。', image: '栋桡之凶,不可以有辅也。' }, + { type: 'yang', text: '栋隆,吉。有它吝。', image: '栋隆之吉,不桡乎下也。' }, + { type: 'yin', text: '枯杨生华,老妇得其士夫,无咎无誉。', image: '枯杨生华,何可久也。老妇士夫,亦可丑也。' }, + { type: 'yin', text: '过涉灭顶,凶,无咎。', image: '过涉之凶,不可咎也。' } + ] + }; + + this.ALL_HEXAGRAMS[29] = { + number: 29, + name: '坎', + symbol: '䷜', + binary: '010010', + upperTrigram: '坎', + lowerTrigram: '坎', + meaning: '坎险,危险,陷溺', + judgment: '习坎,有孚,维心亨,行有尚。', + image: '水洊至,习坎。君子以常德行,习教事。', + guidance: '面临重重困难和危险。保持诚信和坚定的信念,终将度过难关。', + career: '事业面临重重困难,需要谨慎应对。保持信心,不要放弃。', + relationship: '关系陷入困难时期,需要共同努力克服。', + keyMessage: '度过险关', + actionAdvice: '保持诚信,坚定信念', + philosophy: '水重叠而来,象征重重险难。君子应保持常德,熟习政教。', + lines: [ + { type: 'yin', text: '习坎,入于坎窞,凶。', image: '习坎入坎,失道凶也。' }, + { type: 'yang', text: '坎有险,求小得。', image: '求小得,未出中也。' }, + { type: 'yin', text: '来之坎坎,险且枕,入于坎窞,勿用。', image: '来之坎坎,终无功也。' }, + { type: 'yin', text: '樽酒簋贰,用缶,纳约自牖,终无咎。', image: '樽酒簋贰,刚柔际也。' }, + { type: 'yang', text: '坎不盈,祇既平,无咎。', image: '坎不盈,中未大也。' }, + { type: 'yin', text: '系用徽纆,置于丛棘,三岁不得,凶。', image: '上六失道,凶三岁也。' } + ] + }; + + this.ALL_HEXAGRAMS[30] = { + number: 30, + name: '离', + symbol: '䷝', + binary: '101101', + upperTrigram: '离', + lowerTrigram: '离', + meaning: '离明,光明,附丽', + judgment: '利贞,亨。畜牝牛,吉。', + image: '明两作,离。大人以继明照于四方。', + guidance: '光明普照,事业顺利。保持光明正大的态度,将获得吉祥。', + career: '事业光明,前景良好。保持正直,继续努力。', + relationship: '关系明朗,感情深厚。保持真诚,珍惜彼此。', + keyMessage: '光明正大', + actionAdvice: '保持光明,继续努力', + philosophy: '光明两次升起,象征光明。大人应连续光明,照耀四方。', + lines: [ + { type: 'yang', text: '履错然,敬之,无咎。', image: '履错之敬,以辟咎也。' }, + { type: 'yin', text: '黄离,元吉。', image: '黄离元吉,得中道也。' }, + { type: 'yang', text: '日昃之离,不鼓缶而歌,则大耋之嗟,凶。', image: '日昃之离,何可久也。' }, + { type: 'yang', text: '突如其来如,焚如,死如,弃如。', image: '突如其来如,无所容也。' }, + { type: 'yin', text: '出涕沱若,戚嗟若,吉。', image: '六五之吉,离王公也。' }, + { type: 'yang', text: '王用出征,有嘉折首,获匪其丑,无咎。', image: '王用出征,以正邦也。' } + ] + }; + + this.ALL_HEXAGRAMS[31] = { + number: 31, + name: '咸', + symbol: '䷞', + binary: '110100', + upperTrigram: '兑', + lowerTrigram: '艮', + meaning: '感应,情感,交流', + judgment: '亨,利贞。取女吉。', + image: '山上有泽,咸。君子以虚受人。', + guidance: '这是情感交流和感应的时期。保持开放的心态,真诚交流,将带来和谐。', + career: '适合加强团队交流,增进感情。良好的人际关系将带来成功。', + relationship: '感情交流顺畅,适合深化关系。真诚相待,感情将更加深厚。', + keyMessage: '真诚感应', + actionAdvice: '开放交流,真诚相待', + philosophy: '山上有泽,二气感应。君子应虚心接受他人。', + lines: [ + { type: 'yin', text: '咸其拇。', image: '咸其拇,志在外也。' }, + { type: 'yin', text: '咸其腓,凶,居吉。', image: '虽凶居吉,顺不害也。' }, + { type: 'yang', text: '咸其股,执其随,往吝。', image: '咸其股,亦不处也。志在随人,所执下也。' }, + { type: 'yang', text: '贞吉,悔亡。憧憧往来,朋从尔思。', image: '贞吉悔亡,未感害也。憧憧往来,未光大也。' }, + { type: 'yin', text: '咸其脢,无悔。', image: '咸其脢,志末也。' }, + { type: 'yin', text: '咸其辅颊舌。', image: '咸其辅颊舌,滕口说也。' } + ] + }; + + this.ALL_HEXAGRAMS[32] = { + number: 32, + name: '恒', + symbol: '䷟', + binary: '001110', + upperTrigram: '震', + lowerTrigram: '巽', + meaning: '恒久,持久,稳定', + judgment: '亨,无咎,利贞。利有攸往。', + image: '雷风,恒。君子以立不易方。', + guidance: '建立长期稳定的关系或事业。持之以恒,坚持不懈,将获得持久成功。', + career: '建立长期稳定的事业基础。坚持原则,持续发展。', + relationship: '建立持久稳定的感情关系。相互理解,长久相伴。', + keyMessage: '持之以恒', + actionAdvice: '坚持原则,长期发展', + philosophy: '雷风相随,象征恒久。君子应确立不变的原则。', + lines: [ + { type: 'yin', text: '浚恒,贞凶,无攸利。', image: '浚恒之凶,始求深也。' }, + { type: 'yang', text: '悔亡。', image: '九二悔亡,能久中也。' }, + { type: 'yang', text: '不恒其德,或承之羞,贞吝。', image: '不恒其德,无所容也。' }, + { type: 'yin', text: '田无禽。', image: '久非其位,安得禽也。' }, + { type: 'yin', text: '恒其德,贞,妇人吉,夫子凶。', image: '妇人贞吉,从一而终也。夫子制义,从妇凶也。' }, + { type: 'yin', text: '振恒,凶。', image: '振恒在上,大无功也。' } + ] + }; + + this.ALL_HEXAGRAMS[33] = { + number: 33, + name: '遁', + symbol: '䷠', + binary: '111100', + upperTrigram: '乾', + lowerTrigram: '艮', + meaning: '退避,隐退,退让', + judgment: '亨,小利贞。', + image: '天下有山,遁。君子以远小人,不恶而严。', + guidance: '这是需要退避的时期。面对不利局面,明智的选择是暂时退让,保全实力。', + career: '事业遇到阻力时,学会适时退让,保存实力等待时机。', + relationship: '关系紧张时,适当保持距离,避免冲突升级。', + keyMessage: '适时退避', + actionAdvice: '保存实力,等待时机', + philosophy: '天下有山,象征退避。君子应远离小人,不恶而严。', + lines: [ + { type: 'yin', text: '遁尾,厉,勿用有攸往。', image: '遁尾之厉,不往何灾也。' }, + { type: 'yin', text: '执之用黄牛之革,莫之胜说。', image: '执用黄牛,固志也。' }, + { type: 'yang', text: '系遁,有疾厉,畜臣妾吉。', image: '系遁之厉,有疾惫也。畜臣妾吉,不可大事也。' }, + { type: 'yang', text: '好遁,君子吉,小人否。', image: '君子好遁,小人否也。' }, + { type: 'yang', text: '嘉遁,贞吉。', image: '嘉遁贞吉,以正志也。' }, + { type: 'yang', text: '肥遁,无不利。', image: '肥遁无不利,无所疑也。' } + ] + }; + + this.ALL_HEXAGRAMS[34] = { + number: 34, + name: '大壮', + symbol: '䷡', + binary: '111000', + upperTrigram: '震', + lowerTrigram: '乾', + meaning: '大壮,强壮,强盛', + judgment: '利贞。', + image: '雷在天上,大壮。君子以非礼弗履。', + guidance: '力量强盛的时期,但要谨慎使用力量,遵循正道,避免滥用。', + career: '事业处于强盛期,但要谨慎行事,避免过度扩张。', + relationship: '关系力量对比明显,强者要体恤弱者,保持平衡。', + keyMessage: '谨慎用强', + actionAdvice: '遵循正道,避免滥用', + philosophy: '雷在天上,象征强大。君子应不行非礼之事。', + lines: [ + { type: 'yang', text: '壮于趾,征凶,有孚。', image: '壮于趾,其孚穷也。' }, + { type: 'yang', text: '贞吉。', image: '九二贞吉,以中也。' }, + { type: 'yang', text: '小人用壮,君子用罔,贞厉。羝羊触藩,羸其角。', image: '小人用壮,君子罔也。' }, + { type: 'yang', text: '贞吉,悔亡。藩决不羸,壮于大舆之輹。', image: '藩决不羸,尚往也。' }, + { type: 'yin', text: '丧羊于易,无悔。', image: '丧羊于易,位不当也。' }, + { type: 'yin', text: '羝羊触藩,不能退,不能遂,无攸利,艰则吉。', image: '不能退,不能遂,不祥也。艰则吉,咎不长也。' } + ] + }; + + this.ALL_HEXAGRAMS[35] = { + number: 35, + name: '晋', + symbol: '䷢', + binary: '101000', + upperTrigram: '离', + lowerTrigram: '坤', + meaning: '晋升,前进,发展', + judgment: '康侯用锡马蕃庶,昼日三接。', + image: '明出地上,晋。君子以自昭明德。', + guidance: '事业和地位将获晋升。光明正大前进,必获成功。', + career: '事业将迎来晋升和发展机遇。把握机会,光明正大前进。', + relationship: '关系将向前发展,地位提升。保持谦逊,继续努力。', + keyMessage: '光明晋升', + actionAdvice: '把握机会,光明正大', + philosophy: '光明出现在地上,象征晋升。君子应彰显自己的美德。', + lines: [ + { type: 'yin', text: '晋如摧如,贞吉。罔孚,裕无咎。', image: '晋如摧如,独行正也。裕无咎,未受命也。' }, + { type: 'yin', text: '晋如愁如,贞吉。受兹介福,于其王母。', image: '受兹介福,以中正也。' }, + { type: 'yang', text: '众允,悔亡。', image: '众允之志,上行也。' }, + { type: 'yang', text: '晋如鼫鼠,贞厉。', image: '鼫鼠贞厉,位不当也。' }, + { type: 'yin', text: '悔亡,失得勿恤,往吉,无不利。', image: '失得勿恤,往有庆也。' }, + { type: 'yang', text: '晋其角,维用伐邑,厉吉,无咎,贞吝。', image: '维用伐邑,道未光也。' } + ] + }; + + // 第36-50卦的完整数据 + this.ALL_HEXAGRAMS[36] = { + number: 36, + name: '明夷', + symbol: '䷣', + binary: '010001', + upperTrigram: '坤', + lowerTrigram: '离', + meaning: '明夷,光明受损,韬光养晦', + judgment: '利艰贞。', + image: '明入地中,明夷。君子以莅众,用晦而明。', + guidance: '光明受损的时期,需要韬光养晦,隐藏实力,等待时机。', + career: '事业受阻时,要低调行事,保存实力,等待东山再起。', + relationship: '关系出现阴影时,要包容理解,给彼此空间。', + keyMessage: '韬光养晦', + actionAdvice: '隐藏实力,等待时机', + philosophy: '光明进入地中,象征光明受损。君子治理众人,用晦而明。', + lines: [ + { type: 'yin', text: '明夷于飞,垂其翼。君子于行,三日不食。有攸往,主人有言。', image: '君子于行,义不食也。' }, + { type: 'yin', text: '明夷,夷于左股,用拯马壮,吉。', image: '六二之吉,顺以则也。' }, + { type: 'yang', text: '明夷于南狩,得其大首,不可疾贞。', image: '南狩之志,乃大得也。' }, + { type: 'yin', text: '入于左腹,获明夷之心,于出门庭。', image: '入于左腹,获心意也。' }, + { type: 'yin', text: '箕子之明夷,利贞。', image: '箕子之贞,明不可息也。' }, + { type: 'yin', text: '不明晦,初登于天,后入于地。', image: '初登于天,照四国也。后入于地,失则也。' } + ] + }; + + this.ALL_HEXAGRAMS[37] = { + number: 37, + name: '家人', + symbol: '䷤', + binary: '110101', + upperTrigram: '巽', + lowerTrigram: '离', + meaning: '家人,家庭,家人关系', + judgment: '利女贞。', + image: '风自火出,家人。君子以言有物而行有恒。', + guidance: '注重家庭关系,建立和谐家庭。家庭成员各守其位,家庭和睦。', + career: '事业与家庭要平衡,家庭和睦是事业成功的基础。', + relationship: '建立和谐的家庭关系,相互理解,共同经营。', + keyMessage: '家庭和睦', + actionAdvice: '各守其位,和谐相处', + philosophy: '风从火出,象征家人。君子说话有内容,行为有常度。', + lines: [ + { type: 'yang', text: '闲有家,悔亡。', image: '闲有家,志未变也。' }, + { type: 'yin', text: '无攸遂,在中馈,贞吉。', image: '六二之吉,顺以巽也。' }, + { type: 'yang', text: '家人嗃嗃,悔厉吉。妇子嘻嘻,终吝。', image: '家人嗃嗃,未失也。妇子嘻嘻,失家节也。' }, + { type: 'yin', text: '富家,大吉。', image: '富家大吉,顺在位也。' }, + { type: 'yang', text: '王假有家,勿恤,吉。', image: '王假有家,交相爱也。' }, + { type: 'yang', text: '有孚威如,终吉。', image: '威如之吉,反身之谓也。' } + ] + }; + + this.ALL_HEXAGRAMS[38] = { + number: 38, + name: '睽', + symbol: '䷥', + binary: '010110', + upperTrigram: '离', + lowerTrigram: '兑', + meaning: '睽违,分离,矛盾', + judgment: '小事吉。', + image: '上火下泽,睽。君子以同而异。', + guidance: '存在分歧和矛盾的时期。求同存异,小处着手,逐步化解矛盾。', + career: '团队存在分歧,需要求同存异,从小事做起改善关系。', + relationship: '关系出现裂痕,需要沟通理解,小步改善。', + keyMessage: '求同存异', + actionAdvice: '小处着手,逐步改善', + philosophy: '上火下泽,象征相违。君子应同中有异,异中求同。', + lines: [ + { type: 'yang', text: '悔亡。丧马勿逐,自复。见恶人无咎。', image: '见恶人,以辟咎也。' }, + { type: 'yin', text: '遇主于巷,无咎。', image: '遇主于巷,未失道也。' }, + { type: 'yin', text: '见舆曳,其牛掣,其人天且劓,无初有终。', image: '见舆曳,位不当也。无初有终,遇刚也。' }, + { type: 'yang', text: '睽孤,遇元夫,交孚,厉无咎。', image: '交孚无咎,志行也。' }, + { type: 'yin', text: '悔亡。厥宗噬肤,往何咎。', image: '厥宗噬肤,往有庆也。' }, + { type: 'yang', text: '睽孤,见豕负涂,载鬼一车,先张之弧,后说之弧,匪寇婚媾,往遇雨则吉。', image: '遇雨之吉,群疑亡也。' } + ] + }; + + this.ALL_HEXAGRAMS[39] = { + number: 39, + name: '蹇', + symbol: '䷦', + binary: '001010', + upperTrigram: '坎', + lowerTrigram: '艮', + meaning: '蹇难,困难,险阻', + judgment: '利西南,不利东北。利见大人,贞吉。', + image: '山上有水,蹇。君子以反身修德。', + guidance: '面临重重困难的时期。反省自身,修德养性,寻求贵人帮助。', + career: '事业遇到重大困难,需要反省改进,寻求指导。', + relationship: '关系遇到困难,需要反思自身,改善沟通。', + keyMessage: '反省修德', + actionAdvice: '寻求指导,改善自身', + philosophy: '山上有水,象征险阻。君子应反省自身,修养德行。', + lines: [ + { type: 'yin', text: '往蹇,来誉。', image: '往蹇来誉,宜待也。' }, + { type: 'yang', text: '王臣蹇蹇,匪躬之故。', image: '王臣蹇蹇,终无尤也。' }, + { type: 'yin', text: '往蹇,来反。', image: '往蹇来反,内喜之也。' }, + { type: 'yin', text: '往蹇,来连。', image: '往蹇来连,当位实也。' }, + { type: 'yang', text: '大蹇,朋来。', image: '大蹇朋来,以中节也。' }, + { type: 'yin', text: '往蹇,来硕,吉。利见大人。', image: '往蹇来硕,志在内也。利见大人,以从贵也。' } + ] + }; + + this.ALL_HEXAGRAMS[40] = { + number: 40, + name: '解', + symbol: '䷧', + binary: '010100', + upperTrigram: '震', + lowerTrigram: '坎', + meaning: '解除,解脱,解决', + judgment: '利西南。无所往,其来复吉。有攸往,夙吉。', + image: '雷雨作,解。君子以赦过宥罪。', + guidance: '困难即将解除,问题得到解决。释放压力,重新开始。', + career: '事业困境即将解除,可以重新开始发展。', + relationship: '关系中的矛盾将得到化解,重获和谐。', + keyMessage: '解除困境', + actionAdvice: '释放压力,重新开始', + philosophy: '雷雨大作,象征解除。君子应赦免过错,宽宥罪恶。', + lines: [ + { type: 'yin', text: '无咎。', image: '刚柔之际,义无咎也。' }, + { type: 'yang', text: '田获三狐,得黄矢,贞吉。', image: '九二贞吉,得中道也。' }, + { type: 'yin', text: '负且乘,致寇至,贞吝。', image: '负且乘,亦可丑也。自我致戎,又谁咎也。' }, + { type: 'yang', text: '解而拇,朋至斯孚。', image: '解而拇,未当位也。' }, + { type: 'yin', text: '君子维有解,吉,有孚于小人。', image: '君子有解,小人退也。' }, + { type: 'yang', text: '公用射隼于高墉之上,获之,无不利。', image: '公用射隼,以解悖也。' } + ] + }; + + this.ALL_HEXAGRAMS[41] = { + number: 41, + name: '损', + symbol: '䷨', + binary: '110001', + upperTrigram: '艮', + lowerTrigram: '兑', + meaning: '减损,减少,损失', + judgment: '有孚,元吉,无咎,可贞。利有攸往。曷之用?二簋可用享。', + image: '山下有泽,损。君子以惩忿窒欲。', + guidance: '需要减损和放弃的时期。减少不必要的欲望和开支,将获得更大收益。', + career: '事业需要精简和优化,减少不必要的开支,提高效率。', + relationship: '关系中需要减少争执和欲望,增进理解和包容。', + keyMessage: '精简减损', + actionAdvice: '减少欲望,增进效率', + philosophy: '山下有泽,象征减损。君子应抑制愤怒,遏制欲望。', + lines: [ + { type: 'yang', text: '已事遄往,无咎,酌损之。', image: '已事遄往,尚合志也。' }, + { type: 'yang', text: '利贞,征凶,弗损益之。', image: '九二利贞,中以为志也。' }, + { type: 'yin', text: '三人行,则损一人;一人行,则得其友。', image: '一人行,三则疑也。' }, + { type: 'yin', text: '损其疾,使遄有喜,无咎。', image: '损其疾,亦可喜也。' }, + { type: 'yin', text: '或益之,十朋之龟弗克违,元吉。', image: '六五元吉,自上佑也。' }, + { type: 'yang', text: '弗损益之,无咎,贞吉。利有攸往,得臣无家。', image: '弗损益之,大得志也。' } + ] + }; + + this.ALL_HEXAGRAMS[42] = { + number: 42, + name: '益', + symbol: '䷩', + binary: '100011', + upperTrigram: '巽', + lowerTrigram: '震', + meaning: '增益,增加,受益', + judgment: '利有攸往,利涉大川。', + image: '风雷,益。君子以见善则迁,有过则改。', + guidance: '这是增益和发展的时期。积极行动,将获得丰厚回报。', + career: '事业将迎来发展和增益,积极行动,把握机会。', + relationship: '关系将得到增进和改善,相互帮助,共同进步。', + keyMessage: '积极增益', + actionAdvice: '积极行动,把握机会', + philosophy: '风雷相助,象征增益。君子应见善则迁,有过则改。', + lines: [ + { type: 'yang', text: '利用为大作,元吉,无咎。', image: '元吉无咎,下不厚事也。' }, + { type: 'yin', text: '或益之,十朋之龟弗克违,永贞吉。王用享于帝,吉。', image: '或益之,自外来也。' }, + { type: 'yin', text: '益之用凶事,无咎。有孚中行,告公用圭。', image: '益用凶事,固有之也。' }, + { type: 'yang', text: '中行,告公从。利用为依迁国。', image: '告公从,以益志也。' }, + { type: 'yang', text: '有孚惠心,勿问元吉。有孚惠我德。', image: '有孚惠心,勿问之矣。惠我德,大得志也。' }, + { type: 'yin', text: '莫益之,或击之,立心勿恒,凶。', image: '莫益之,偏辞也。或击之,自外来也。' } + ] + }; + + this.ALL_HEXAGRAMS[43] = { + number: 43, + name: '夬', + symbol: '䷪', + binary: '111110', + upperTrigram: '兑', + lowerTrigram: '乾', + meaning: '决断,决裂,果断', + judgment: '扬于王庭,孚号有厉。告自邑,不利即戎,利有攸往。', + image: '泽上于天,夬。君子以施禄及下,居德则忌。', + guidance: '需要果断决断的时期。面对问题要当机立断,不可犹豫不决。', + career: '事业需要做出重大决策,果断行动,避免拖延。', + relationship: '关系中需要做出决断,避免藕断丝连。', + keyMessage: '果断决断', + actionAdvice: '当机立断,避免拖延', + philosophy: '泽水在天上,象征决断。君子应施恩于下,积德忌满。', + lines: [ + { type: 'yang', text: '壮于前趾,往不胜为咎。', image: '不胜而往,咎也。' }, + { type: 'yang', text: '惕号,莫夜有戎,勿恤。', image: '有戎勿恤,得中道也。' }, + { type: 'yang', text: '壮于頄,有凶。君子夬夬,独行遇雨,若濡有愠,无咎。', image: '君子夬夬,终无咎也。' }, + { type: 'yang', text: '臀无肤,其行次且。牵羊悔亡,闻言不信。', image: '其行次且,位不当也。闻言不信,聪不明也。' }, + { type: 'yang', text: '苋陆夬夬,中行无咎。', image: '中行无咎,中未光也。' }, + { type: 'yin', text: '无号,终有凶。', image: '无号之凶,终不可长也。' } + ] + }; + + this.ALL_HEXAGRAMS[44] = { + number: 44, + name: '姤', + symbol: '䷫', + binary: '011111', + upperTrigram: '乾', + lowerTrigram: '巽', + meaning: '相遇,邂逅,阴遇阳', + judgment: '女壮,勿用取女。', + image: '天下有风,姤。后以施命诰四方。', + guidance: '不期而遇的时期。小心处理新出现的关系,避免被不良影响。', + career: '事业中遇到新的机遇或挑战,要谨慎处理。', + relationship: '可能遇到新的感情或关系,需要谨慎对待。', + keyMessage: '谨慎相遇', + actionAdvice: '小心处理新关系', + philosophy: '天下有风,象征相遇。君主应发布命令,告诫四方。', + lines: [ + { type: 'yin', text: '系于金柅,贞吉。有攸往,见凶,羸豕孚蹢躅。', image: '系于金柅,柔道牵也。' }, + { type: 'yang', text: '包有鱼,无咎,不利宾。', image: '包有鱼,义不及宾也。' }, + { type: 'yang', text: '臀无肤,其行次且,厉,无大咎。', image: '其行次且,行未牵也。' }, + { type: 'yang', text: '包无鱼,起凶。', image: '无鱼之凶,远民也。' }, + { type: 'yang', text: '以杞包瓜,含章,有陨自天。', image: '九五含章,中正也。有陨自天,志不舍命也。' }, + { type: 'yang', text: '姤其角,吝,无咎。', image: '姤其角,上穷吝也。' } + ] + }; + + // 第45-64卦的完整数据 + this.ALL_HEXAGRAMS[45] = { + number: 45, + name: '萃', + symbol: '䷬', + binary: '000110', + upperTrigram: '兑', + lowerTrigram: '坤', + meaning: '聚集,集合,汇聚', + judgment: '亨。王假有庙,利见大人,亨,利贞。用大牲吉,利有攸往。', + image: '泽上于地,萃。君子以除戎器,戒不虞。', + guidance: '这是聚集和团结的时期。集合力量,共同奋斗,将获得更大成功。', + career: '事业需要集合团队力量,共同奋斗,实现更大目标。', + relationship: '关系需要加强团结,共同面对挑战,增进感情。', + keyMessage: '集合力量', + actionAdvice: '团结奋斗,共同前进', + philosophy: '泽水在地上,象征聚集。君子应修整兵器,戒备不测。', + lines: [ + { type: 'yin', text: '有孚不终,乃乱乃萃,若号,一握为笑,勿恤,往无咎。', image: '乃乱乃萃,其志乱也。' }, + { type: 'yin', text: '引吉,无咎,孚乃利用禴。', image: '引吉无咎,中未变也。' }, + { type: 'yin', text: '萃如,嗟如,无攸利,往无咎,小吝。', image: '往无咎,上巽也。' }, + { type: 'yang', text: '大吉,无咎。', image: '大吉无咎,位不当也。' }, + { type: 'yang', text: '萃有位,无咎。匪孚,元永贞,悔亡。', image: '萃有位,志未光也。' }, + { type: 'yin', text: '赍咨涕洟,无咎。', image: '赍咨涕洟,未安上也。' } + ] + }; + + this.ALL_HEXAGRAMS[46] = { + number: 46, + name: '升', + symbol: '䷭', + binary: '011000', + upperTrigram: '坤', + lowerTrigram: '巽', + meaning: '上升,晋升,发展', + judgment: '元亨,用见大人,勿恤,南征吉。', + image: '地中生木,升。君子以顺德,积小以高大。', + guidance: '事业和地位将不断上升。循序渐进,终将获得高位。', + career: '事业将迎来上升期,循序渐进,稳步发展。', + relationship: '关系将不断升温,感情日益深厚。', + keyMessage: '循序渐进', + actionAdvice: '稳步发展,终获成功', + philosophy: '地中生出树木,象征上升。君子应顺应德行,积小成大。', + lines: [ + { type: 'yin', text: '允升,大吉。', image: '允升大吉,上合志也。' }, + { type: 'yang', text: '孚乃利用禴,无咎。', image: '九二之孚,有喜也。' }, + { type: 'yang', text: '升虚邑。', image: '升虚邑,无所疑也。' }, + { type: 'yin', text: '王用亨于岐山,吉,无咎。', image: '王用亨于岐山,顺事也。' }, + { type: 'yang', text: '贞吉,升阶。', image: '贞吉升阶,大得志也。' }, + { type: 'yin', text: '冥升,利于不息之贞。', image: '冥升在上,消不富也。' } + ] + }; + + this.ALL_HEXAGRAMS[47] = { + number: 47, + name: '困', + symbol: '䷮', + binary: '110010', + upperTrigram: '兑', + lowerTrigram: '坎', + meaning: '困穷,困境,困顿', + judgment: '亨,贞,大人吉,无咎。有言不信。', + image: '泽无水,困。君子以致命遂志。', + guidance: '处于困境的时期。虽处困境,但保持正道,终将脱困。', + career: '事业陷入困境,需要坚持正道,寻求突破。', + relationship: '关系陷入僵局,需要耐心化解,坚持信念。', + keyMessage: '坚持正道', + actionAdvice: '保持信念,寻求突破', + philosophy: '泽中无水,象征困穷。君子应舍命达成志向。', + lines: [ + { type: 'yin', text: '臀困于株木,入于幽谷,三岁不觌。', image: '入于幽谷,幽不明也。' }, + { type: 'yang', text: '困于酒食,朱绂方来,利用享祀,征凶,无咎。', image: '困于酒食,中有庆也。' }, + { type: 'yin', text: '困于石,据于蒺藜,入于其宫,不见其妻,凶。', image: '据于蒺藜,乘刚也。入于其宫,不见其妻,不祥也。' }, + { type: 'yang', text: '来徐徐,困于金车,吝,有终。', image: '来徐徐,志在下也。虽不当位,有与也。' }, + { type: 'yang', text: '劓刖,困于赤绂,乃徐有说,利用祭祀。', image: '劓刖志未得也。乃徐有说,以中直也。利用祭祀,受福也。' }, + { type: 'yin', text: '困于葛藟,于臲卼,曰动悔。有悔,征吉。', image: '困于葛藟,未当也。动悔有悔,吉行也。' } + ] + }; + + this.ALL_HEXAGRAMS[48] = { + number: 48, + name: '井', + symbol: '䷯', + binary: '001011', + upperTrigram: '坎', + lowerTrigram: '巽', + meaning: '水井,养人,恒常', + judgment: '改邑不改井,无丧无得,往来井井。汔至,亦未繘井,羸其瓶,凶。', + image: '木上有水,井。君子以劳民劝相。', + guidance: '建立持久的基础,滋养他人。保持恒常,持续改善。', + career: '建立稳固的事业基础,持续发展,滋养团队。', + relationship: '建立稳定的关系基础,相互滋养,长久发展。', + keyMessage: '建立基础', + actionAdvice: '持续改善,滋养他人', + philosophy: '木上有水,象征水井。君子应慰劳民众,劝勉相助。', + lines: [ + { type: 'yin', text: '井泥不食,旧井无禽。', image: '井泥不食,下也。旧井无禽,时舍也。' }, + { type: 'yang', text: '井谷射鲋,瓮敝漏。', image: '井谷射鲋,无与也。' }, + { type: 'yang', text: '井渫不食,为我心恻。可用汲,王明,并受其福。', image: '井渫不食,行恻也。求王明,受福也。' }, + { type: 'yin', text: '井甃,无咎。', image: '井甃无咎,修井也。' }, + { type: 'yang', text: '井冽,寒泉食。', image: '寒泉之食,中正也。' }, + { type: 'yin', text: '井收勿幕,有孚元吉。', image: '元吉在上,大成也。' } + ] + }; + + this.ALL_HEXAGRAMS[49] = { + number: 49, + name: '革', + symbol: '䷰', + binary: '101110', + upperTrigram: '兑', + lowerTrigram: '离', + meaning: '变革,改革,革命', + judgment: '己日乃孚,元亨利贞,悔亡。', + image: '泽中有火,革。君子以治历明时。', + guidance: '这是重大变革的时期。顺应时势,主动改革,将获得新生。', + career: '事业需要重大变革,破旧立新,开创新局面。', + relationship: '关系需要深度调整,改变旧模式,建立新关系。', + keyMessage: '主动变革', + actionAdvice: '破旧立新,顺应时势', + philosophy: '泽中有火,象征变革。君子应修治历法,明确时令。', + lines: [ + { type: 'yang', text: '巩用黄牛之革。', image: '巩用黄牛,不可以有为也。' }, + { type: 'yin', text: '己日乃革之,征吉,无咎。', image: '己日革之,行有嘉也。' }, + { type: 'yang', text: '征凶,贞厉。革言三就,有孚。', image: '革言三就,又何之矣。' }, + { type: 'yang', text: '悔亡,有孚改命,吉。', image: '改命之吉,信志也。' }, + { type: 'yang', text: '大人虎变,未占有孚。', image: '大人虎变,其文炳也。' }, + { type: 'yin', text: '君子豹变,小人革面,征凶,居贞吉。', image: '君子豹变,其文蔚也。小人革面,顺以从君也。' } + ] + }; + + this.ALL_HEXAGRAMS[50] = { + number: 50, + name: '鼎', + symbol: '䷱', + binary: '011101', + upperTrigram: '离', + lowerTrigram: '巽', + meaning: '鼎器,烹饪,养贤', + judgment: '元吉,亨。', + image: '木上有火,鼎。君子以正位凝命。', + guidance: '建立新秩序,培养人才。正位凝命,成就大业。', + career: '事业建立新体制,培养团队,成就大业。', + relationship: '关系建立新秩序,相互培养,共同成长。', + keyMessage: '建立新秩序', + actionAdvice: '培养人才,成就大业', + philosophy: '木上有火,象征鼎器。君子应端正位置,凝聚使命。', + lines: [ + { type: 'yin', text: '鼎颠趾,利出否,得妾以其子,无咎。', image: '鼎颠趾,未悖也。利出否,以从贵也。' }, + { type: 'yin', text: '鼎有实,我仇有疾,不我能即,吉。', image: '鼎有实,慎所之也。我仇有疾,终无尤也。' }, + { type: 'yang', text: '鼎耳革,其行塞,雉膏不食,方雨亏悔,终吉。', image: '鼎耳革,失其义也。' }, + { type: 'yang', text: '鼎折足,覆公餗,其形渥,凶。', image: '覆公餗,信如何也。' }, + { type: 'yin', text: '鼎黄耳金铉,利贞。', image: '鼎黄耳,中以为实也。' }, + { type: 'yang', text: '鼎玉铉,大吉,无不利。', image: '玉铉在上,刚柔节也。' } + ] + }; + + this.ALL_HEXAGRAMS[51] = { + number: 51, + name: '震', + symbol: '䷲', + binary: '100100', + upperTrigram: '震', + lowerTrigram: '震', + meaning: '震动,惊雷,行动', + judgment: '亨。震来虩虩,笑言哑哑。震惊百里,不丧匕鬯。', + image: '洊雷,震。君子以恐惧修省。', + guidance: '面临重大震动和挑战。保持冷静,化危为机,获得成长。', + career: '事业面临重大变化,保持镇定,化挑战为机遇。', + relationship: '关系经历震荡,需要冷静处理,增进理解。', + keyMessage: '化危为机', + actionAdvice: '保持冷静,获得成长', + philosophy: '雷声重叠,象征震动。君子应因恐惧而修身反省。', + lines: [ + { type: 'yang', text: '震来虩虩,后笑言哑哑,吉。', image: '震来虩虩,恐致福也。笑言哑哑,后有则也。' }, + { type: 'yin', text: '震来厉,亿丧贝,跻于九陵,勿逐,七日得。', image: '震来厉,乘刚也。' }, + { type: 'yin', text: '震苏苏,震行无眚。', image: '震苏苏,位不当也。' }, + { type: 'yin', text: '震遂泥。', image: '震遂泥,未光也。' }, + { type: 'yin', text: '震往来厉,亿无丧,有事。', image: '震往来厉,危行也。其事在中,大无丧也。' }, + { type: 'yin', text: '震索索,视矍矍,征凶。震不于其躬,于其邻,无咎。婚媾有言。', image: '震索索,中未得也。虽凶无咎,畏邻戒也。' } + ] + }; + + this.ALL_HEXAGRAMS[52] = { + number: 52, + name: '艮', + symbol: '䷳', + binary: '001001', + upperTrigram: '艮', + lowerTrigram: '艮', + meaning: '艮止,停止,静止', + judgment: '艮其背,不获其身,行其庭,不见其人,无咎。', + image: '兼山,艮。君子以思不出其位。', + guidance: '需要停止和反思的时期。适可而止,保持内心平静。', + career: '事业需要暂停调整,避免过度扩张,保持稳健。', + relationship: '关系需要冷静期,给彼此空间,避免冲突。', + keyMessage: '适可而止', + actionAdvice: '保持平静,避免冲动', + philosophy: '两山重叠,象征停止。君子思考不超越本位。', + lines: [ + { type: 'yin', text: '艮其趾,无咎,利永贞。', image: '艮其趾,未失正也。' }, + { type: 'yin', text: '艮其腓,不拯其随,其心不快。', image: '不拯其随,未退听也。' }, + { type: 'yang', text: '艮其限,列其夤,厉薰心。', image: '艮其限,危薰心也。' }, + { type: 'yin', text: '艮其身,无咎。', image: '艮其身,止诸躬也。' }, + { type: 'yin', text: '艮其辅,言有序,悔亡。', image: '艮其辅,以中正也。' }, + { type: 'yang', text: '敦艮,吉。', image: '敦艮之吉,以厚终也。' } + ] + }; + + this.ALL_HEXAGRAMS[53] = { + number: 53, + name: '渐', + symbol: '䷴', + binary: '001011', + upperTrigram: '巽', + lowerTrigram: '艮', + meaning: '渐进,逐步,循序渐进', + judgment: '女归吉,利贞。', + image: '山上有木,渐。君子以居贤德,善俗。', + guidance: '循序渐进发展的时期。稳步前进,终将达成目标。', + career: '事业需要循序渐进,稳步发展,避免急躁。', + relationship: '关系需要慢慢培养,循序渐进,建立深厚感情。', + keyMessage: '循序渐进', + actionAdvice: '稳步前进,终达目标', + philosophy: '山上生木,象征渐进。君子应居守贤德,改善风俗。', + lines: [ + { type: 'yin', text: '鸿渐于干,小子厉,有言,无咎。', image: '小子之厉,义无咎也。' }, + { type: 'yin', text: '鸿渐于磐,饮食衎衎,吉。', image: '饮食衎衎,不素饱也。' }, + { type: 'yang', text: '鸿渐于陆,夫征不复,妇孕不育,凶。利御寇。', image: '夫征不复,离群丑也。妇孕不育,失其道也。利用御寇,顺相保也。' }, + { type: 'yin', text: '鸿渐于木,或得其桷,无咎。', image: '或得其桷,顺以巽也。' }, + { type: 'yang', text: '鸿渐于陵,妇三岁不孕,终莫之胜,吉。', image: '终莫之胜,吉,得所愿也。' }, + { type: 'yang', text: '鸿渐于逵,其羽可用为仪,吉。', image: '其羽可用为仪,吉,不可乱也。' } + ] + }; + + this.ALL_HEXAGRAMS[54] = { + number: 54, + name: '归妹', + symbol: '䷵', + binary: '110100', + upperTrigram: '震', + lowerTrigram: '兑', + meaning: '归妹,嫁女,归宿', + judgment: '征凶,无攸利。', + image: '泽上有雷,归妹。君子以永终知敝。', + guidance: '关注归宿和结果的时期。谨慎处理终身大事,避免草率。', + career: '事业需要谨慎决策,避免草率行动导致不利后果。', + relationship: '关系涉及终身大事,需要慎重考虑,避免冲动。', + keyMessage: '谨慎归宿', + actionAdvice: '慎重决策,避免草率', + philosophy: '泽上有雷,象征嫁女。君子应知永恒终结之理。', + lines: [ + { type: 'yang', text: '归妹以娣,跛能履,征吉。', image: '归妹以娣,以恒也。跛能履,吉相承也。' }, + { type: 'yang', text: '眇能视,利幽人之贞。', image: '利幽人之贞,未变常也。' }, + { type: 'yin', text: '归妹以须,反归以娣。', image: '归妹以须,未当也。' }, + { type: 'yang', text: '归妹愆期,迟归有时。', image: '愆期之志,有待而行也。' }, + { type: 'yin', text: '帝乙归妹,其君之袂,不如其娣之袂良,月几望,吉。', image: '帝乙归妹,不如其娣之袂良也。其位在中,以贵行也。' }, + { type: 'yin', text: '女承筐无实,士刲羊无血,无攸利。', image: '上六无实,承虚筐也。' } + ] + }; + + this.ALL_HEXAGRAMS[55] = { + number: 55, + name: '丰', + symbol: '䷶', + binary: '101100', + upperTrigram: '震', + lowerTrigram: '离', + meaning: '丰盛,盛大,丰富', + judgment: '亨,王假之,勿忧,宜日中。', + image: '雷电皆至,丰。君子以折狱致刑。', + guidance: '事业丰盛盛大的时期。保持中正,避免过度,才能长久。', + career: '事业达到丰盛期,需要保持谦逊,避免骄傲自满。', + relationship: '关系丰盛美满,需要珍惜维护,避免过度。', + keyMessage: '保持中正', + actionAdvice: '珍惜丰盛,避免过度', + philosophy: '雷电俱至,象征丰盛。君子应断案用刑。', + lines: [ + { type: 'yang', text: '遇其配主,虽旬无咎,往有尚。', image: '虽旬无咎,过旬灾也。' }, + { type: 'yin', text: '丰其蔀,日中见斗,往得疑疾,有孚发若,吉。', image: '有孚发若,信以发志也。' }, + { type: 'yang', text: '丰其沛,日中见沫,折其右肱,无咎。', image: '丰其沛,不可大事也。折其右肱,终不可用也。' }, + { type: 'yang', text: '丰其蔀,日中见斗,遇其夷主,吉。', image: '丰其蔀,位不当也。日中见斗,幽不明也。遇其夷主,吉,行也。' }, + { type: 'yin', text: '来章,有庆誉,吉。', image: '六五之吉,有庆也。' }, + { type: 'yin', text: '丰其屋,蔀其家,窥其户,阒其无人,三岁不觌,凶。', image: '丰其屋,天际翔也。窥其户,阒其无人,自藏也。' } + ] + }; + + this.ALL_HEXAGRAMS[56] = { + number: 56, + name: '旅', + symbol: '䷷', + binary: '101001', + upperTrigram: '离', + lowerTrigram: '艮', + meaning: '旅行,旅居,漂泊', + judgment: '小亨,旅贞吉。', + image: '山上有火,旅。君子以明慎用刑,而不留狱。', + guidance: '处于旅行或漂泊的时期。保持谨慎,适应环境,终将安定。', + career: '事业处于变动期,需要适应新环境,保持谨慎。', + relationship: '关系处于不稳定期,需要相互理解,共同适应。', + keyMessage: '适应环境', + actionAdvice: '保持谨慎,终将安定', + philosophy: '山上有火,象征旅行。君子应明慎用刑,不留狱讼。', + lines: [ + { type: 'yin', text: '旅琐琐,斯其所取灾。', image: '旅琐琐,志穷灾也。' }, + { type: 'yin', text: '旅即次,怀其资,得童仆贞。', image: '得童仆贞,终无尤也。' }, + { type: 'yang', text: '旅焚其次,丧其童仆,贞厉。', image: '旅焚其次,亦以伤矣。以旅与下,其义丧也。' }, + { type: 'yang', text: '旅于处,得其资斧,我心不快。', image: '旅于处,未得位也。得其资斧,心未快也。' }, + { type: 'yin', text: '射雉一矢亡,终以誉命。', image: '终以誉命,上逮也。' }, + { type: 'yang', text: '鸟焚其巢,旅人先笑后号咷。丧牛于易,凶。', image: '以旅在上,其义焚也。丧牛于易,终莫之闻也。' } + ] + }; + + this.ALL_HEXAGRAMS[57] = { + number: 57, + name: '巽', + symbol: '䷸', + binary: '110110', + upperTrigram: '巽', + lowerTrigram: '巽', + meaning: '巽顺,顺从,谦逊', + judgment: '小亨,利攸往,利见大人。', + image: '随风,巽。君子以申命行事。', + guidance: '以谦逊柔顺的态度行事。顺应时势,将获得顺利发展。', + career: '事业需要谦逊态度,顺应时势,获得发展机会。', + relationship: '关系需要谦逊包容,相互理解,增进和谐。', + keyMessage: '谦逊柔顺', + actionAdvice: '顺应时势,获得发展', + philosophy: '风相随,象征顺从。君子应重申命令,推行政事。', + lines: [ + { type: 'yin', text: '进退,利武人之贞。', image: '进退,志疑也。利武人之贞,志治也。' }, + { type: 'yang', text: '巽在床下,用史巫纷若,吉,无咎。', image: '纷若之吉,得中也。' }, + { type: 'yang', text: '频巽,吝。', image: '频巽之吝,志穷也。' }, + { type: 'yin', text: '悔亡,田获三品。', image: '田获三品,有功也。' }, + { type: 'yang', text: '贞吉悔亡,无不利。无初有终,先庚三日,后庚三日,吉。', image: '九五之吉,位正中也。' }, + { type: 'yin', text: '巽在床下,丧其资斧,贞凶。', image: '巽在床下,上穷也。丧其资斧,正乎凶也。' } + ] + }; + + this.ALL_HEXAGRAMS[58] = { + number: 58, + name: '兑', + symbol: '䷹', + binary: '110011', + upperTrigram: '兑', + lowerTrigram: '兑', + meaning: '兑悦,喜悦,和悦', + judgment: '亨,利贞。', + image: '丽泽,兑。君子以朋友讲习。', + guidance: '充满喜悦和和谐的氛围。保持和悦态度,增进人际和谐。', + career: '事业处于和谐期,保持愉悦心情,促进团队合作。', + relationship: '关系和谐美满,保持喜悦心情,增进感情。', + keyMessage: '喜悦和谐', + actionAdvice: '保持和悦,增进和谐', + philosophy: '两泽相连,象征喜悦。君子应与朋友讲习道义。', + lines: [ + { type: 'yang', text: '和兑,吉。', image: '和兑之吉,行未疑也。' }, + { type: 'yang', text: '孚兑,吉,悔亡。', image: '孚兑之吉,信志也。' }, + { type: 'yin', text: '来兑,凶。', image: '来兑之凶,位不当也。' }, + { type: 'yang', text: '商兑,未宁,介疾有喜。', image: '九四之喜,有庆也。' }, + { type: 'yin', text: '孚于剥,有厉。', image: '孚于剥,位正当也。' }, + { type: 'yang', text: '引兑。', image: '上六引兑,未光也。' } + ] + }; + + this.ALL_HEXAGRAMS[59] = { + number: 59, + name: '涣', + symbol: '䷺', + binary: '010011', + upperTrigram: '巽', + lowerTrigram: '坎', + meaning: '涣散,离散,化解', + judgment: '亨。王假有庙,利涉大川,利贞。', + image: '风行水上,涣。先王以享于帝立庙。', + guidance: '化解涣散,重建秩序的时期。凝聚人心,重获团结。', + career: '团队出现涣散,需要重建凝聚力,恢复秩序。', + relationship: '关系出现疏离,需要沟通理解,重建联系。', + keyMessage: '化解涣散', + actionAdvice: '凝聚人心,重建团结', + philosophy: '风行水上,象征涣散。先王享祭上帝,建立宗庙。', + lines: [ + { type: 'yin', text: '用拯马壮,吉。', image: '初六之吉,顺也。' }, + { type: 'yang', text: '涣奔其机,悔亡。', image: '涣奔其机,得愿也。' }, + { type: 'yin', text: '涣其躬,无悔。', image: '涣其躬,志在外也。' }, + { type: 'yin', text: '涣其群,元吉。涣有丘,匪夷所思。', image: '涣其群,元吉,光大也。' }, + { type: 'yang', text: '涣汗其大号,涣王居,无咎。', image: '王居无咎,正位也。' }, + { type: 'yang', text: '涣其血,去逖出,无咎。', image: '涣其血,远害也。' } + ] + }; + + this.ALL_HEXAGRAMS[60] = { + number: 60, + name: '节', + symbol: '䷻', + binary: '110010', + upperTrigram: '坎', + lowerTrigram: '兑', + meaning: '节制,节约,适度', + judgment: '亨。苦节,不可贞。', + image: '泽上有水,节。君子以制数度,议德行。', + guidance: '需要节制和适度的时期。合理控制,避免过度,保持平衡。', + career: '事业需要合理规划,节制资源,避免浪费。', + relationship: '关系需要适度控制,给彼此空间,保持平衡。', + keyMessage: '合理节制', + actionAdvice: '适度控制,保持平衡', + philosophy: '泽上有水,象征节制。君子应制定法度,评议德行。', + lines: [ + { type: 'yin', text: '不出户庭,无咎。', image: '不出户庭,知通塞也。' }, + { type: 'yang', text: '不出门庭,凶。', image: '不出门庭,失时极也。' }, + { type: 'yin', text: '不节若,则嗟若,无咎。', image: '不节之嗟,又谁咎也。' }, + { type: 'yang', text: '安节,亨。', image: '安节之亨,承上道也。' }, + { type: 'yang', text: '甘节,吉,往有尚。', image: '甘节之吉,居位中也。' }, + { type: 'yin', text: '苦节,贞凶,悔亡。', image: '苦节贞凶,其道穷也。' } + ] + }; + + this.ALL_HEXAGRAMS[61] = { + number: 61, + name: '中孚', + symbol: '䷼', + binary: '110011', + upperTrigram: '巽', + lowerTrigram: '兑', + meaning: '中孚,诚信,内心诚实', + judgment: '豚鱼吉,利涉大川,利贞。', + image: '泽上有风,中孚。君子以议狱缓死。', + guidance: '以诚信为本的时期。内心诚实,将获得信任和成功。', + career: '事业以诚信为本,建立信誉,获得成功。', + relationship: '关系以诚信为基础,建立深厚信任。', + keyMessage: '诚信为本', + actionAdvice: '内心诚实,建立信任', + philosophy: '泽上有风,象征诚信。君子应审议案件,宽缓死刑。', + lines: [ + { type: 'yang', text: '虞吉,有它不燕。', image: '初九虞吉,志未变也。' }, + { type: 'yang', text: '鸣鹤在阴,其子和之。我有好爵,吾与尔靡之。', image: '其子和之,中心愿也。' }, + { type: 'yin', text: '得敌,或鼓或罢,或泣或歌。', image: '或鼓或罢,位不当也。' }, + { type: 'yang', text: '月几望,马匹亡,无咎。', image: '马匹亡,绝类上也。' }, + { type: 'yang', text: '有孚挛如,无咎。', image: '有孚挛如,位正当也。' }, + { type: 'yin', text: '翰音登于天,贞凶。', image: '翰音登于天,何可长也。' } + ] + }; + + this.ALL_HEXAGRAMS[62] = { + number: 62, + name: '小过', + symbol: '䷽', + binary: '001100', + upperTrigram: '震', + lowerTrigram: '艮', + meaning: '小过,小有过失,过度', + judgment: '亨,利贞。可小事,不可大事。飞鸟遗之音,不宜上宜下,大吉。', + image: '山上有雷,小过。君子以行过乎恭,丧过乎哀,用过乎俭。', + guidance: '小有过失的时期。谨慎行事,避免大错,小处改进。', + career: '事业中可能出现小失误,及时调整,避免扩大。', + relationship: '关系中小有摩擦,及时化解,增进理解。', + keyMessage: '谨慎小过', + actionAdvice: '小处改进,避免大错', + philosophy: '山上有雷,象征小过。君子行为过于恭谨,丧事过于哀痛,用度过于节俭。', + lines: [ + { type: 'yin', text: '飞鸟以凶。', image: '飞鸟以凶,不可如何也。' }, + { type: 'yin', text: '过其祖,遇其妣,不及其君,遇其臣,无咎。', image: '不及其君,臣不可过也。' }, + { type: 'yang', text: '弗过防之,从或戕之,凶。', image: '从或戕之,凶如何也。' }, + { type: 'yang', text: '无咎,弗过遇之。往厉必戒,勿用永贞。', image: '弗过遇之,位不当也。往厉必戒,终不可长也。' }, + { type: 'yin', text: '密云不雨,自我西郊,公弋取彼在穴。', image: '密云不雨,已上也。' }, + { type: 'yin', text: '弗遇过之,飞鸟离之,凶,是谓灾眚。', image: '弗遇过之,已亢也。' } + ] + }; + + this.ALL_HEXAGRAMS[63] = { + number: 63, + name: '既济', + symbol: '䷾', + binary: '101010', + upperTrigram: '坎', + lowerTrigram: '离', + meaning: '既济,完成,成功', + judgment: '亨,小利贞,初吉终乱。', + image: '水在火上,既济。君子以思患而豫防之。', + guidance: '事情已经完成的时期。居安思危,预防未然,保持成果。', + career: '事业已经取得成功,需要防范风险,保持成果。', + relationship: '关系已经稳定,需要维护感情,预防问题。', + keyMessage: '居安思危', + actionAdvice: '预防未然,保持成果', + philosophy: '水在火上,象征完成。君子应思患而预防。', + lines: [ + { type: 'yang', text: '曳其轮,濡其尾,无咎。', image: '曳其轮,义无咎也。' }, + { type: 'yin', text: '妇丧其茀,勿逐,七日得。', image: '七日得,以中道也。' }, + { type: 'yang', text: '高宗伐鬼方,三年克之,小人勿用。', image: '三年克之,惫也。' }, + { type: 'yin', text: '繻有衣袽,终日戒。', image: '终日戒,有所疑也。' }, + { type: 'yang', text: '东邻杀牛,不如西邻之禴祭,实受其福。', image: '东邻杀牛,不如西邻之时也。实受其福,吉大来也。' }, + { type: 'yin', text: '濡其首,厉。', image: '濡其首厉,何可久也。' } + ] + }; + + this.ALL_HEXAGRAMS[64] = { + number: 64, + name: '未济', + symbol: '䷿', + binary: '101010', + upperTrigram: '离', + lowerTrigram: '坎', + meaning: '尚未完成,过渡,混乱', + judgment: '亨。小狐汔济,濡其尾,无攸利。', + image: '火在水上,未济。君子以慎辨物居方。', + guidance: '事情尚未完成,处于过渡阶段。需要非常谨慎,仔细辨别情况,不要急于求成,否则可能功亏一篑。', + career: '项目或任务接近尾声,但仍有风险。必须保持警惕,仔细检查细节,确保万无一失。', + relationship: '关系处于一个不确定的过渡期。需要谨慎沟通,明确方向,才能进入下一个阶段。', + keyMessage: '功亏一篑前的谨慎', + actionAdvice: '谨慎辨别,三思而后行', + philosophy: '事物在完成前充满变数,君子应谨慎分辨,找准自己的位置。', + lines: [ + { type: 'yin', text: '濡其尾,吝。', image: '亦不知极也。' }, + { type: 'yang', text: '曳其轮,贞吉。', image: '中以行正也。' }, + { type: 'yin', text: '未济,征凶,利涉大川。', image: '未济征凶,位不当也。' }, + { type: 'yang', text: '贞吉,悔亡。震用伐鬼方,三年有赏于大国。', image: '贞吉悔亡,志行也。' }, + { type: 'yin', text: '贞吉,无悔。君子之光,有孚,吉。', image: '君子之光,其晖吉也。' }, + { type: 'yang', text: '有孚于饮酒,无咎。濡其首,有孚失是。', image: '饮酒濡首,亦不知节也。' } + ] + }; + } +} + +module.exports = YijingAnalyzer; \ No newline at end of file diff --git a/server/services/yijingAnalyzer.js b/server/services/yijingAnalyzer.js new file mode 100644 index 0000000..e69de29 diff --git a/server/services/ziweiAnalyzer.cjs b/server/services/ziweiAnalyzer.cjs new file mode 100644 index 0000000..9366bf0 --- /dev/null +++ b/server/services/ziweiAnalyzer.cjs @@ -0,0 +1,447 @@ +// 紫微斗数分析服务模块 +// 完全基于logic/ziwei.txt的原始逻辑实现 + +class ZiweiAnalyzer { + constructor() { + this.heavenlyStems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + this.earthlyBranches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + this.palaceNames = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫']; + this.majorStars = ['紫微', '天机', '太阳', '武曲', '天同', '廉贞', '天府', '太阴', '贪狼', '巨门', '天相', '天梁', '七杀', '破军']; + } + + // 真正的紫微斗数分析函数 + performRealZiweiAnalysis(birth_data) { + const { name, birth_date, birth_time, gender } = birth_data; + const personName = name || '您'; + const personGender = gender === 'male' || gender === '男' ? '男性' : '女性'; + + // 计算八字信息 + const baziInfo = this.calculateBazi(birth_date, birth_time); + + // 计算紫微斗数排盘 + const starChart = this.calculateRealStarChart(birth_date, birth_time, gender); + + // 生成基于真实星盘的个性化分析 + const analysis = this.generateRealPersonalizedAnalysis(starChart, personName, personGender, baziInfo); + + return { + analysis_type: 'ziwei', + analysis_date: new Date().toISOString().split('T')[0], + basic_info: { + personal_data: { + name: personName, + birth_date: birth_date, + birth_time: birth_time || '12:00', + gender: personGender + }, + bazi_info: baziInfo + }, + ziwei_analysis: { + ming_gong: starChart.mingGong, + ming_gong_xing: starChart.mingGongStars, + shi_er_gong: starChart.twelvePalaces, + si_hua: starChart.siHua, + da_xian: starChart.majorPeriods, + birth_chart: starChart.birthChart + }, + detailed_analysis: analysis + }; + } + + // 计算真正的八字信息 + calculateBazi(birthDateStr, birthTimeStr) { + const birthDate = new Date(birthDateStr); + const [hour, minute] = birthTimeStr ? birthTimeStr.split(':').map(Number) : [12, 0]; + + // 计算干支(简化版,实际应该使用更精确的天文计算) + const year = birthDate.getFullYear(); + const month = birthDate.getMonth() + 1; + const day = birthDate.getDate(); + + const yearStemIndex = (year - 4) % 10; + const yearBranchIndex = (year - 4) % 12; + + // 计算月柱(基于节气) + const monthStemIndex = ((yearStemIndex * 2 + month + 1) % 10 + 10) % 10; + const monthBranchIndex = (month + 1) % 12; + + // 计算日柱(简化计算) + const baseDate = new Date(1900, 0, 31); + const daysDiff = Math.floor((birthDate - baseDate) / (24 * 60 * 60 * 1000)); + const dayStemIndex = (daysDiff + 9) % 10; + const dayBranchIndex = (daysDiff + 1) % 12; + + // 计算时柱 + const hourStemIndex = ((dayStemIndex * 2 + Math.floor(hour / 2) + 2) % 10 + 10) % 10; + const hourBranchIndex = Math.floor((hour + 1) / 2) % 12; + + return { + year: this.heavenlyStems[yearStemIndex] + this.earthlyBranches[yearBranchIndex], + month: this.heavenlyStems[monthStemIndex] + this.earthlyBranches[monthBranchIndex], + day: this.heavenlyStems[dayStemIndex] + this.earthlyBranches[dayBranchIndex], + hour: this.heavenlyStems[hourStemIndex] + this.earthlyBranches[hourBranchIndex], + birth_info: { + year, + month, + day, + hour, + minute + } + }; + } + + // 计算真正的紫微斗数排盘 + calculateRealStarChart(birthDateStr, birthTimeStr, gender) { + const birthDate = new Date(birthDateStr); + const [hour, minute] = birthTimeStr ? birthTimeStr.split(':').map(Number) : [12, 0]; + + const year = birthDate.getFullYear(); + const month = birthDate.getMonth() + 1; + const day = birthDate.getDate(); + + // 根据出生时间计算命宫位置(真正的紫微斗数算法) + const mingGongIndex = this.calculateMingGongPosition(month, hour); + const mingGong = this.earthlyBranches[mingGongIndex]; + + // 计算紫微星位置 + const ziweiPosition = this.calculateZiweiPosition(day, mingGongIndex); + + // 排布十四主星 + const starPositions = this.arrangeMainStars(ziweiPosition, mingGongIndex); + + // 计算十二宫位 + const twelvePalaces = this.calculateTwelvePalaces(mingGongIndex, starPositions); + + // 计算四化 + const siHua = this.calculateSiHua(year); + + // 计算大限 + const majorPeriods = this.calculateMajorPeriods(mingGongIndex, gender); + + return { + mingGong: mingGong, + mingGongStars: starPositions[mingGongIndex] || [], + twelvePalaces: twelvePalaces, + siHua: siHua, + majorPeriods: majorPeriods, + birthChart: this.generateBirthChart(twelvePalaces, starPositions) + }; + } + + // 计算命宫位置 + calculateMingGongPosition(month, hour) { + // 紫微斗数命宫计算公式:寅宫起正月,顺数至生月,再从生月宫逆数至生时 + const monthPosition = (month + 1) % 12; // 寅宫起正月 + const hourBranch = Math.floor((hour + 1) / 2) % 12; + const mingGongPosition = (monthPosition - hourBranch + 12) % 12; + return mingGongPosition; + } + + // 计算紫微星位置 + calculateZiweiPosition(day, mingGongIndex) { + // 简化的紫微星定位算法 + const ziweiBase = (day - 1) % 12; + return (mingGongIndex + ziweiBase) % 12; + } + + // 排布十四主星 + arrangeMainStars(ziweiPosition, mingGongIndex) { + const starPositions = {}; + + // 紫微星系 + starPositions[ziweiPosition] = ['紫微']; + starPositions[(ziweiPosition + 1) % 12] = ['天机']; + starPositions[(ziweiPosition + 2) % 12] = ['太阳']; + starPositions[(ziweiPosition + 3) % 12] = ['武曲']; + starPositions[(ziweiPosition + 4) % 12] = ['天同']; + starPositions[(ziweiPosition + 5) % 12] = ['廉贞']; + + // 天府星系(对宫起) + const tianfuPosition = (ziweiPosition + 6) % 12; + starPositions[tianfuPosition] = ['天府']; + starPositions[(tianfuPosition + 1) % 12] = ['太阴']; + starPositions[(tianfuPosition + 2) % 12] = ['贪狼']; + starPositions[(tianfuPosition + 3) % 12] = ['巨门']; + starPositions[(tianfuPosition + 4) % 12] = ['天相']; + starPositions[(tianfuPosition + 5) % 12] = ['天梁']; + starPositions[(tianfuPosition + 6) % 12] = ['七杀']; + starPositions[(tianfuPosition + 7) % 12] = ['破军']; + + return starPositions; + } + + // 计算十二宫位 + calculateTwelvePalaces(mingGongIndex, starPositions) { + const palaces = {}; + + for (let i = 0; i < 12; i++) { + const palaceIndex = (mingGongIndex + i) % 12; + const palaceName = this.palaceNames[i]; + + palaces[palaceName] = { + position: this.earthlyBranches[palaceIndex], + stars: starPositions[palaceIndex] || [], + interpretation: this.interpretPalace(palaceName, starPositions[palaceIndex] || []), + strength: this.calculatePalaceStrength(starPositions[palaceIndex] || []) + }; + } + + return palaces; + } + + // 计算四化 + calculateSiHua(year) { + const yearStemIndex = (year - 4) % 10; + const siHuaMap = { + 0: { lu: '廉贞', quan: '破军', ke: '武曲', ji: '太阳' }, // 甲年 + 1: { lu: '天机', quan: '天梁', ke: '紫微', ji: '太阴' }, // 乙年 + 2: { lu: '天同', quan: '天机', ke: '文昌', ji: '廉贞' }, // 丙年 + 3: { lu: '太阴', quan: '天同', ke: '天机', ji: '巨门' }, // 丁年 + 4: { lu: '贪狼', quan: '太阴', ke: '右弼', ji: '天机' }, // 戊年 + 5: { lu: '武曲', quan: '贪狼', ke: '天梁', ji: '文曲' }, // 己年 + 6: { lu: '太阳', quan: '武曲', ke: '太阴', ji: '天同' }, // 庚年 + 7: { lu: '巨门', quan: '太阳', ke: '文曲', ji: '文昌' }, // 辛年 + 8: { lu: '天梁', quan: '紫微', ke: '左辅', ji: '武曲' }, // 壬年 + 9: { lu: '破军', quan: '巨门', ke: '太阴', ji: '贪狼' } // 癸年 + }; + + return siHuaMap[yearStemIndex] || siHuaMap[0]; + } + + // 计算大限 + calculateMajorPeriods(mingGongIndex, gender) { + const periods = []; + const isMale = gender === 'male' || gender === '男'; + const startAge = isMale ? 4 : 4; // 简化处理,实际需要根据五行局计算 + + for (let i = 0; i < 12; i++) { + const ageStart = startAge + i * 10; + const ageEnd = ageStart + 9; + const palaceIndex = isMale ? (mingGongIndex + i) % 12 : (mingGongIndex - i + 12) % 12; + + periods.push({ + age_range: `${ageStart}-${ageEnd}岁`, + palace: this.earthlyBranches[palaceIndex], + palace_name: this.palaceNames[i], + description: `${ageStart}-${ageEnd}岁大限在${this.earthlyBranches[palaceIndex]}宫` + }); + } + + return periods; + } + + // 解释宫位 + interpretPalace(palaceName, stars) { + const interpretations = { + '命宫': '代表个人的性格、外貌、才能和一生的命运走向', + '兄弟宫': '代表兄弟姐妹关系、朋友关系和合作伙伴', + '夫妻宫': '代表婚姻状况、配偶特质和感情生活', + '子女宫': '代表子女缘分、创造力和部属关系', + '财帛宫': '代表财运、理财能力和金钱观念', + '疾厄宫': '代表健康状况、疾病倾向和意外灾厄', + '迁移宫': '代表外出运、变动和人际关系', + '交友宫': '代表朋友关系、社交能力和人脉网络', + '事业宫': '代表事业发展、工作状况和社会地位', + '田宅宫': '代表不动产、居住环境和家庭状况', + '福德宫': '代表精神享受、兴趣爱好和福分', + '父母宫': '代表父母关系、长辈缘分和权威关系' + }; + + let interpretation = interpretations[palaceName] || '此宫位的基本含义'; + + if (stars.length > 0) { + interpretation += `。主星为${stars.join('、')},`; + interpretation += this.getStarInfluence(stars[0]); + } + + return interpretation; + } + + // 计算宫位强度 + calculatePalaceStrength(stars) { + if (stars.length === 0) return '平'; + + const strongStars = ['紫微', '天府', '太阳', '武曲', '天同']; + const hasStrongStar = stars.some(star => strongStars.includes(star)); + + return hasStrongStar ? '旺' : '平'; + } + + // 获取星曜影响 + getStarInfluence(star) { + const influences = { + '紫微': '具有领导才能和贵气,适合担任管理职务', + '天机': '聪明机智,善于策划,适合从事智力工作', + '太阳': '光明磊落,具有权威性,适合公职或领导工作', + '武曲': '意志坚强,执行力强,适合财经或技术工作', + '天同': '性格温和,人缘好,适合服务性工作', + '廉贞': '个性鲜明,有艺术天分,适合创意工作', + '天府': '稳重可靠,有组织能力,适合管理工作', + '太阴': '细腻敏感,直觉力强,适合文艺或服务工作', + '贪狼': '多才多艺,善于交际,适合业务或娱乐工作', + '巨门': '口才好,分析力强,适合教育或传媒工作', + '天相': '忠诚可靠,协调能力强,适合辅助性工作', + '天梁': '正直善良,有长者风范,适合教育或公益工作', + '七杀': '勇敢果断,开拓性强,适合竞争性工作', + '破军': '创新求变,不拘传统,适合开创性工作' + }; + + return influences[star] || '具有独特的个性特质'; + } + + // 生成出生图 + generateBirthChart(twelvePalaces, starPositions) { + const chart = {}; + + Object.keys(twelvePalaces).forEach(palaceName => { + const palace = twelvePalaces[palaceName]; + chart[palaceName] = { + position: palace.position, + stars: palace.stars, + strength: palace.strength + }; + }); + + return chart; + } + + // 生成基于真实星盘的个性化分析 + generateRealPersonalizedAnalysis(starChart, personName, personGender, baziInfo) { + const mingGongStars = starChart.mingGongStars; + const mainStar = mingGongStars[0] || '无主星'; + + return { + personality_analysis: { + main_traits: `${personName}的命宫主星为${mainStar},${this.getStarInfluence(mainStar)}`, + character_description: `根据紫微斗数分析,${personName}具有${mainStar}星的特质,${personGender}特有的温和与坚韧并存`, + strengths: this.getPersonalityStrengths(mainStar), + weaknesses: this.getPersonalityWeaknesses(mainStar) + }, + career_fortune: { + suitable_fields: this.getSuitableCareerFields(starChart.twelvePalaces['事业宫']), + development_advice: this.getCareerDevelopmentAdvice(mainStar, personGender), + peak_periods: this.getCareerPeakPeriods(starChart.majorPeriods) + }, + wealth_fortune: { + wealth_potential: this.getWealthPotential(starChart.twelvePalaces['财帛宫']), + investment_advice: this.getInvestmentAdvice(mainStar), + financial_planning: this.getFinancialPlanning(personGender) + }, + relationship_fortune: { + marriage_outlook: this.getMarriageOutlook(starChart.twelvePalaces['夫妻宫'], personGender), + ideal_partner: this.getIdealPartnerTraits(mainStar, personGender), + relationship_advice: this.getRelationshipAdvice(mainStar) + }, + health_fortune: { + health_tendencies: this.getHealthTendencies(starChart.twelvePalaces['疾厄宫']), + wellness_advice: this.getWellnessAdvice(mainStar), + prevention_focus: this.getPreventionFocus(baziInfo) + }, + life_guidance: { + overall_fortune: `${personName}一生运势以${mainStar}星为主导,${this.getOverallFortune(mainStar)}`, + key_life_phases: this.getKeyLifePhases(starChart.majorPeriods), + development_strategy: this.getDevelopmentStrategy(mainStar, personGender) + } + }; + } + + // 获取个性优势 + getPersonalityStrengths(star) { + const strengths = { + '紫微': '领导能力强,有贵人相助,具有权威性', + '天机': '聪明机智,反应敏捷,善于策划', + '太阳': '光明正大,热情开朗,具有感召力', + '武曲': '意志坚定,执行力强,理财有方', + '天同': '性格温和,人际关系好,适应力强' + }; + return strengths[star] || '具有独特的个人魅力'; + } + + // 获取个性弱点 + getPersonalityWeaknesses(star) { + const weaknesses = { + '紫微': '有时过于自信,容易忽视他人意见', + '天机': '思虑过多,有时缺乏行动力', + '太阳': '有时过于直接,可能伤害他人感情', + '武曲': '过于注重物质,有时显得冷漠', + '天同': '有时过于被动,缺乏主见' + }; + return weaknesses[star] || '需要注意平衡发展'; + } + + // 获取适合的职业领域 + getSuitableCareerFields(careerPalace) { + const stars = careerPalace.stars; + if (stars.length === 0) return '适合稳定发展的传统行业'; + + const mainStar = stars[0]; + const fields = { + '紫微': '政府机关、大型企业管理、金融业', + '天机': '科技业、咨询业、教育业', + '太阳': '公务员、媒体业、娱乐业', + '武曲': '金融业、制造业、军警', + '天同': '服务业、医疗业、社会工作' + }; + + return fields[mainStar] || '多元化发展的现代服务业'; + } + + // 其他辅助方法的简化实现 + getCareerDevelopmentAdvice(star, gender) { + return `根据${star}星的特质,建议${gender === '男性' ? '发挥男性的决断力' : '发挥女性的细致性'},在职场中稳步发展`; + } + + getCareerPeakPeriods(periods) { + return periods.slice(2, 5).map(p => p.age_range).join('、'); + } + + getWealthPotential(wealthPalace) { + return wealthPalace.stars.length > 0 ? '财运较佳,适合投资理财' : '财运平稳,宜稳健理财'; + } + + getInvestmentAdvice(star) { + return `根据${star}星的特质,建议选择稳健的投资方式`; + } + + getFinancialPlanning(gender) { + return `${gender === '男性' ? '建议制定长期财务规划' : '建议注重家庭理财平衡'}`; + } + + getMarriageOutlook(marriagePalace, gender) { + return `婚姻宫${marriagePalace.strength === '旺' ? '较旺' : '平稳'},${gender === '男性' ? '适合寻找贤内助' : '适合寻找可靠伴侣'}`; + } + + getIdealPartnerTraits(star, gender) { + return `适合寻找与${star}星互补的伴侣特质`; + } + + getRelationshipAdvice(star) { + return `在感情中发挥${star}星的优势,保持真诚沟通`; + } + + getHealthTendencies(healthPalace) { + return healthPalace.stars.length > 0 ? '需注意相关星曜影响的健康问题' : '整体健康状况良好'; + } + + getWellnessAdvice(star) { + return `根据${star}星的特质,建议保持规律作息,适度运动`; + } + + getPreventionFocus(baziInfo) { + return '根据八字信息,建议注重五行平衡的养生方法'; + } + + getOverallFortune(star) { + return `整体运势受${star}星影响,建议发挥其正面特质`; + } + + getKeyLifePhases(periods) { + return periods.slice(0, 3).map(p => `${p.age_range}为${p.palace_name}大限`).join(','); + } + + getDevelopmentStrategy(star, gender) { + return `建议以${star}星的特质为核心,${gender === '男性' ? '稳健发展' : '平衡发展'},把握人生机遇`; + } +} + +module.exports = ZiweiAnalyzer; \ No newline at end of file diff --git a/server/services/ziweiAnalyzer.js b/server/services/ziweiAnalyzer.js new file mode 100644 index 0000000..e69de29 diff --git a/src/components/AnalysisResultDisplay.tsx b/src/components/AnalysisResultDisplay.tsx index 1fcd305..1f560a8 100644 --- a/src/components/AnalysisResultDisplay.tsx +++ b/src/components/AnalysisResultDisplay.tsx @@ -44,19 +44,24 @@ const AnalysisResultDisplay: React.FC = ({ analysisR // 渲染八字命理分析 const renderBaziAnalysis = () => { - // 如果有 birthDate,使用新的 BaziAnalysisDisplay 组件 + // 如果有分析结果数据,优先使用 ComprehensiveBaziAnalysis 组件 + if (analysisResult && analysisResult.data) { + return ; + } + // 如果有 birthDate 但没有分析结果,使用 BaziAnalysisDisplay 组件 if (birthDate) { return ; } - // 否则使用原来的 ComprehensiveBaziAnalysis 组件(向后兼容) + // 默认使用 ComprehensiveBaziAnalysis 组件(向后兼容) return ; }; // 渲染紫微斗数分析 const renderZiweiAnalysis = () => { - const data = analysisResult?.analysis || analysisResult?.data?.analysis || analysisResult; - const ziweiData = data?.ziwei || data; - const analysisData = data?.analysis || data; + // 处理新的数据结构: { type: 'ziwei', data: analysisResult } + const data = analysisResult?.data || analysisResult; + const ziweiData = data?.ziwei_analysis || data?.ziwei || data; + const analysisData = data?.detailed_analysis || data?.analysis || data; @@ -204,7 +209,8 @@ const AnalysisResultDisplay: React.FC = ({ analysisR // 渲染易经占卜分析 const renderYijingAnalysis = () => { - const data = analysisResult?.analysis || analysisResult?.data?.analysis || analysisResult; + // 处理新的数据结构: { type: 'yijing', data: analysisResult } + const data = analysisResult?.data || analysisResult; return (
diff --git a/src/components/BaziAnalysisDisplay.tsx b/src/components/BaziAnalysisDisplay.tsx index 0080582..f6ec998 100644 --- a/src/components/BaziAnalysisDisplay.tsx +++ b/src/components/BaziAnalysisDisplay.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer } from 'recharts'; import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from './ui/Card'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; interface BaziAnalysisDisplayProps { birthDate: { @@ -85,10 +85,10 @@ const BaziAnalysisDisplay: React.FC = ({ birthDate }) // 并行调用两个函数 const [baziDetailsResponse, wuxingAnalysisResponse] = await Promise.all([ - supabase.functions.invoke('bazi-details', { + localApi.functions.invoke('bazi-details', { body: requestBody }), - supabase.functions.invoke('bazi-wuxing-analysis', { + localApi.functions.invoke('bazi-wuxing-analysis', { body: requestBody }) ]); diff --git a/src/components/ComprehensiveBaziAnalysis.tsx b/src/components/ComprehensiveBaziAnalysis.tsx index fdcface..0f2d18a 100644 --- a/src/components/ComprehensiveBaziAnalysis.tsx +++ b/src/components/ComprehensiveBaziAnalysis.tsx @@ -22,7 +22,8 @@ const ComprehensiveBaziAnalysis: React.FC = ({ a return current || defaultValue; }; - const data = analysisResult?.analysis || analysisResult?.data?.analysis || analysisResult; + // 处理新的数据结构: { type: 'bazi', data: analysisResult } + const data = analysisResult?.data || analysisResult; // 五行颜色配置 const elementColors: { [key: string]: string } = { @@ -357,7 +358,7 @@ const ComprehensiveBaziAnalysis: React.FC = ({ a

- {safeGet(data, 'wuxing_analysis.personal_traits', '您的日主特征体现了独特的性格魅力...')} + {safeGet(data, 'wuxing_analysis.personality_traits', '您的日主特征体现了独特的性格魅力...')}

@@ -426,7 +427,7 @@ const ComprehensiveBaziAnalysis: React.FC = ({ a

调和建议

- {safeGet(data, 'wuxing_analysis.suggestions', '建议通过特定的方式来平衡五行能量...')} + {safeGet(data, 'wuxing_analysis.improvement_suggestions', '建议通过特定的方式来平衡五行能量...')}

diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 0368409..5bf6958 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,12 +1,11 @@ import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; -import { User } from '@supabase/supabase-js'; -import { supabase } from '../lib/supabase'; +import { localApi, User, AuthResponse } from '../lib/localApi'; interface AuthContextType { user: User | null; loading: boolean; signIn: (email: string, password: string) => Promise; - signUp: (email: string, password: string) => Promise; + signUp: (email: string, password: string, fullName?: string) => Promise; signOut: () => Promise; } @@ -25,39 +24,59 @@ export function AuthProvider({ children }: AuthProviderProps) { async function loadUser() { setLoading(true); try { - const { data: { user } } = await supabase.auth.getUser(); - setUser(user); + const response = await localApi.auth.getUser(); + if (response.data) { + setUser(response.data.user); + } else { + setUser(null); + } + } catch (error) { + console.error('加载用户信息失败:', error); + setUser(null); } finally { setLoading(false); } } loadUser(); - - // Set up auth listener - KEEP SIMPLE, avoid any async operations in callback - const { data: { subscription } } = supabase.auth.onAuthStateChange( - (_event, session) => { - // NEVER use any async operations in callback - setUser(session?.user || null); - } - ); - - return () => subscription.unsubscribe(); }, []); // Auth methods async function signIn(email: string, password: string) { - return await supabase.auth.signInWithPassword({ email, password }); + try { + const response = await localApi.auth.signInWithPassword({ email, password }); + if (response.data) { + setUser(response.data.user); + return { data: response.data, error: null }; + } else { + return { data: null, error: response.error }; + } + } catch (error) { + return { data: null, error: { message: '登录失败' } }; + } } - async function signUp(email: string, password: string) { - return await supabase.auth.signUp({ - email, - password, - }); + async function signUp(email: string, password: string, fullName?: string) { + try { + const response = await localApi.auth.signUp(email, password, fullName); + if (response.data) { + setUser(response.data.user); + return { data: response.data, error: null }; + } else { + return { data: null, error: response.error }; + } + } catch (error) { + return { data: null, error: { message: '注册失败' } }; + } } async function signOut() { - return await supabase.auth.signOut(); + try { + const response = await localApi.auth.signOut(); + setUser(null); + return { error: null }; + } catch (error) { + return { error: { message: '登出失败' } }; + } } return ( diff --git a/src/lib/localApi.ts b/src/lib/localApi.ts new file mode 100644 index 0000000..abde37c --- /dev/null +++ b/src/lib/localApi.ts @@ -0,0 +1,320 @@ +// 本地API客户端 +// 替代Supabase客户端,提供相同的接口 + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001/api'; + +interface ApiResponse { + data?: T; + error?: { + code: string; + message: string; + }; +} + +interface User { + id: number; + email: string; +} + +interface AuthResponse { + user: User; + token: string; +} + +class LocalApiClient { + private token: string | null = null; + + constructor() { + // 从localStorage恢复token + this.token = localStorage.getItem('auth_token'); + } + + // 设置认证token + setToken(token: string | null) { + this.token = token; + if (token) { + localStorage.setItem('auth_token', token); + } else { + localStorage.removeItem('auth_token'); + } + } + + // 获取认证头 + private getAuthHeaders(): Record { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (this.token) { + headers.Authorization = `Bearer ${this.token}`; + } + + return headers; + } + + // 通用请求方法 + private async request( + endpoint: string, + options: RequestInit = {} + ): Promise> { + try { + const url = `${API_BASE_URL}${endpoint}`; + const response = await fetch(url, { + ...options, + headers: { + ...this.getAuthHeaders(), + ...options.headers, + }, + }); + + const data = await response.json(); + + if (!response.ok) { + return { + error: data.error || { + code: 'HTTP_ERROR', + message: `HTTP ${response.status}: ${response.statusText}`, + }, + }; + } + + return { data: data.data }; + } catch (error) { + console.error('API请求错误:', error); + return { + error: { + code: 'NETWORK_ERROR', + message: error instanceof Error ? error.message : '网络请求失败', + }, + }; + } + } + + // 认证相关方法 + auth = { + // 用户注册 + signUp: async (email: string, password: string, full_name?: string): Promise> => { + const response = await this.request('/auth/register', { + method: 'POST', + body: JSON.stringify({ email, password, full_name }), + }); + + if (response.data) { + this.setToken(response.data.token); + } + + return response; + }, + + // 用户登录 + signInWithPassword: async ({ email, password }: { email: string; password: string }): Promise> => { + const response = await this.request('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + + if (response.data) { + this.setToken(response.data.token); + } + + return response; + }, + + // 获取当前用户 + getUser: async (): Promise> => { + return this.request<{ user: User }>('/auth/me'); + }, + + // 用户登出 + signOut: async (): Promise> => { + const response = await this.request<{ message: string }>('/auth/logout', { + method: 'POST', + }); + + this.setToken(null); + return response; + }, + + // 验证token + verify: async (): Promise> => { + return this.request<{ valid: boolean; user: User }>('/auth/verify'); + }, + + // 修改密码 + changePassword: async (currentPassword: string, newPassword: string): Promise> => { + return this.request<{ message: string }>('/auth/change-password', { + method: 'POST', + body: JSON.stringify({ + current_password: currentPassword, + new_password: newPassword, + }), + }); + }, + }; + + // 用户档案相关方法 + profiles = { + // 获取用户档案 + get: async (): Promise> => { + return this.request<{ profile: any }>('/profile'); + }, + + // 更新用户档案 + update: async (profileData: any): Promise> => { + return this.request<{ profile: any }>('/profile', { + method: 'PUT', + body: JSON.stringify(profileData), + }); + }, + + // 上传头像 + uploadAvatar: async (avatarUrl: string): Promise> => { + return this.request<{ message: string; avatar_url: string }>('/profile/avatar', { + method: 'POST', + body: JSON.stringify({ avatar_url: avatarUrl }), + }); + }, + }; + + // 分析相关方法 + analysis = { + // 八字分析 + bazi: async (birthData: any): Promise> => { + return this.request<{ record_id: number; analysis: any }>('/analysis/bazi', { + method: 'POST', + body: JSON.stringify({ birth_data: birthData }), + }); + }, + + // 紫微斗数分析 + ziwei: async (birthData: any): Promise> => { + return this.request<{ record_id: number; analysis: any }>('/analysis/ziwei', { + method: 'POST', + body: JSON.stringify({ birth_data: birthData }), + }); + }, + + // 易经分析 + yijing: async (birthData: any, question?: string): Promise> => { + return this.request<{ record_id: number; analysis: any }>('/analysis/yijing', { + method: 'POST', + body: JSON.stringify({ birth_data: birthData, question }), + }); + }, + + // 综合分析 + comprehensive: async (birthData: any, includeTypes?: string[]): Promise> => { + return this.request<{ record_id: number; analysis: any }>('/analysis/comprehensive', { + method: 'POST', + body: JSON.stringify({ birth_data: birthData, include_types: includeTypes }), + }); + }, + + // 获取分析类型 + getTypes: async (): Promise> => { + return this.request<{ available_types: any[] }>('/analysis/types'); + }, + + // 验证分析数据 + validate: async (birthData: any, analysisType: string): Promise> => { + return this.request<{ valid: boolean; errors: string[] }>('/analysis/validate', { + method: 'POST', + body: JSON.stringify({ birth_data: birthData, analysis_type: analysisType }), + }); + }, + }; + + // 历史记录相关方法 + history = { + // 获取历史记录 + getAll: async (params?: { page?: number; limit?: number; reading_type?: string }): Promise> => { + const searchParams = new URLSearchParams(); + if (params?.page) searchParams.set('page', params.page.toString()); + if (params?.limit) searchParams.set('limit', params.limit.toString()); + if (params?.reading_type) searchParams.set('reading_type', params.reading_type); + + const queryString = searchParams.toString(); + const endpoint = queryString ? `/history?${queryString}` : '/history'; + + return this.request(endpoint); + }, + + // 获取单个记录 + getById: async (id: string): Promise> => { + return this.request(`/history/${id}`); + }, + + // 删除记录 + delete: async (id: string): Promise> => { + return this.request<{ message: string }>(`/history/${id}`, { + method: 'DELETE', + }); + }, + + // 批量删除记录 + deleteBatch: async (ids: string[]): Promise> => { + return this.request<{ message: string }>('/history', { + method: 'DELETE', + body: JSON.stringify({ ids }), + }); + }, + + // 获取统计信息 + getStats: async (): Promise> => { + return this.request('/history/stats/summary'); + }, + + // 搜索记录 + search: async (query: string, params?: { page?: number; limit?: number }): Promise> => { + const searchParams = new URLSearchParams(); + if (params?.page) searchParams.set('page', params.page.toString()); + if (params?.limit) searchParams.set('limit', params.limit.toString()); + + const queryString = searchParams.toString(); + const endpoint = queryString ? `/history/search/${encodeURIComponent(query)}?${queryString}` : `/history/search/${encodeURIComponent(query)}`; + + return this.request(endpoint); + }, + }; + + // 兼容Supabase的functions.invoke方法 + functions = { + invoke: async (functionName: string, options: { body: any }): Promise> => { + // 将Supabase Edge Function调用映射到本地API + const functionMap: Record = { + 'bazi-analyzer': '/analysis/bazi', + 'ziwei-analyzer': '/analysis/ziwei', + 'yijing-analyzer': '/analysis/yijing', + 'bazi-details': '/analysis/bazi-details', + 'bazi-wuxing-analysis': '/analysis/bazi-wuxing', + 'reading-history': '/history', + }; + + const endpoint = functionMap[functionName.replace(/\?.*$/, '')] || `/functions/${functionName}`; + + if (functionName.includes('reading-history')) { + const { action, ...params } = options.body; + + switch (action) { + case 'get_history': + return this.history.getAll(); + case 'delete_reading': + return this.history.delete(params.reading_id); + default: + return { error: { code: 'UNKNOWN_ACTION', message: `Unknown action: ${action}` } }; + } + } + + return this.request(endpoint, { + method: 'POST', + body: JSON.stringify(options.body), + }); + }, + }; +} + +// 创建单例实例 +const localApi = new LocalApiClient(); + +export { localApi }; +export type { ApiResponse, User, AuthResponse }; \ No newline at end of file diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts deleted file mode 100644 index ed10b1f..0000000 --- a/src/lib/supabase.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createClient } from '@supabase/supabase-js' - -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY - -if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables') -} - -export const supabase = createClient(supabaseUrl, supabaseAnonKey) \ No newline at end of file diff --git a/src/pages/AnalysisPage.tsx b/src/pages/AnalysisPage.tsx index 2d639d2..790efb7 100644 --- a/src/pages/AnalysisPage.tsx +++ b/src/pages/AnalysisPage.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; import { Button } from '../components/ui/Button'; import { Input } from '../components/ui/Input'; import { Select } from '../components/ui/Select'; @@ -35,13 +35,9 @@ const AnalysisPage: React.FC = () => { if (!user) return; try { - const { data, error } = await supabase - .from('user_profiles') - .select('*') - .eq('user_id', user.id) - .maybeSingle(); - - if (data) { + const response = await localApi.profiles.get(); + if (response.data && response.data.profile) { + const data = response.data.profile; setProfile(data); setFormData({ name: data.full_name || '', @@ -69,35 +65,32 @@ const AnalysisPage: React.FC = () => { setAnalysisResult(null); try { - // 对于八字分析,直接显示结果,不需要调用 Edge Function - if (analysisType === 'bazi') { - const birthData = { - date: formData.birth_date, - time: formData.birth_time || '12:00' - }; - setAnalysisResult({ type: 'bazi', birthDate: birthData }); - toast.success('分析完成!'); - return; - } - - // 对于其他分析类型,保持原有逻辑 - const analysisRequest: AnalysisRequest = { - user_id: user.id, - reading_type: analysisType, - birth_data: { - name: formData.name, - birth_date: formData.birth_date, - birth_time: formData.birth_time, - gender: formData.gender, - birth_place: formData.birth_place, - ...(analysisType === 'yijing' && { question: formData.question }) - } + const birthData = { + name: formData.name, + birth_date: formData.birth_date, + birth_time: formData.birth_time, + gender: formData.gender, + birth_place: formData.birth_place }; - const functionName = `${analysisType}-analyzer?_t=${new Date().getTime()}`; - const { data, error } = await supabase.functions.invoke(functionName, { - body: analysisRequest - }); + let response; + + // 根据分析类型调用相应的API + switch (analysisType) { + case 'bazi': + response = await localApi.analysis.bazi(birthData); + break; + case 'ziwei': + response = await localApi.analysis.ziwei(birthData); + break; + case 'yijing': + response = await localApi.analysis.yijing(birthData, formData.question); + break; + default: + throw new Error(`不支持的分析类型: ${analysisType}`); + } + + const { data, error } = response; if (error) { throw error; @@ -107,7 +100,11 @@ const AnalysisPage: React.FC = () => { throw new Error(data.error.message); } - setAnalysisResult(data.data); + // 后端返回格式: { data: { record_id, analysis } } + setAnalysisResult({ + type: analysisType, + data: data.analysis + }); toast.success('分析完成!'); } catch (error: any) { console.error('分析失败:', error); @@ -287,9 +284,12 @@ const AnalysisPage: React.FC = () => { {/* 分析结果 */} {analysisResult && ( )} diff --git a/src/pages/BaziDetailsPage.tsx b/src/pages/BaziDetailsPage.tsx index 50718f4..8b66f88 100644 --- a/src/pages/BaziDetailsPage.tsx +++ b/src/pages/BaziDetailsPage.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Calendar, Clock, Star, BookOpen, Sparkles, User } from 'lucide-react'; import { Button } from '../components/ui/Button'; import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; import { useAuth } from '../contexts/AuthContext'; import { toast } from 'sonner'; @@ -79,7 +79,7 @@ const BaziDetailsPage: React.FC = () => { '阴': 'text-purple-600 bg-purple-50 border-purple-300' }; - // 调用Supabase Edge Function获取八字详细信息 + // 获取八字详细信息 const fetchBaziDetails = async () => { if (!birthDate) { toast.error('请选择您的出生日期'); @@ -90,18 +90,20 @@ const BaziDetailsPage: React.FC = () => { setError(null); try { - // 调用Supabase Edge Function - const { data, error } = await supabase.functions.invoke('bazi-details', { + // 调用本地API + const response = await localApi.functions.invoke('bazi-details', { body: { birthDate, birthTime } }); - if (error) throw error; + if (response.error) { + throw new Error(response.error.message); + } - if (data?.data) { - setBaziData(data.data); + if (response.data?.data) { + setBaziData(response.data.data); toast.success('八字详情分析完成!'); } else { throw new Error('排盘结果为空'); diff --git a/src/pages/HistoryPage.tsx b/src/pages/HistoryPage.tsx index d945645..ccb73b6 100644 --- a/src/pages/HistoryPage.tsx +++ b/src/pages/HistoryPage.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; import { Button } from '../components/ui/Button'; import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; import AnalysisResultDisplay from '../components/AnalysisResultDisplay'; @@ -24,22 +24,13 @@ const HistoryPage: React.FC = () => { try { setLoading(true); - const { data, error } = await supabase.functions.invoke('reading-history', { - body: { - action: 'get_history', - user_id: user.id - } - }); + const response = await localApi.history.getAll(); - if (error) { - throw error; + if (response.error) { + throw new Error(response.error.message); } - if (data?.error) { - throw new Error(data.error.message); - } - - const historyData = data.data || []; + const historyData = response.data || []; // 数据转换适配器:将旧格式转换为新格式 const processedData = historyData.map((reading: any) => { @@ -84,15 +75,10 @@ const HistoryPage: React.FC = () => { } try { - const { error } = await supabase.functions.invoke('reading-history', { - body: { - action: 'delete_reading', - reading_id: readingId - } - }); + const response = await localApi.history.delete(readingId); - if (error) { - throw error; + if (response.error) { + throw new Error(response.error.message); } setReadings(prev => prev.filter(r => r.id !== readingId)); diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index c610d2f..f96dc8f 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; import { Button } from '../components/ui/Button'; import { Input } from '../components/ui/Input'; import { Select } from '../components/ui/Select'; @@ -30,17 +30,14 @@ const ProfilePage: React.FC = () => { if (!user) return; try { - const { data, error } = await supabase - .from('user_profiles') - .select('*') - .eq('user_id', user.id) - .maybeSingle(); - - if (error && error.code !== 'PGRST116') { - throw error; + const response = await localApi.profiles.get(); + + if (response.error) { + throw new Error(response.error.message); } - if (data) { + if (response.data && response.data.profile) { + const data = response.data.profile; setProfile(data); setFormData({ full_name: data.full_name || '', @@ -65,37 +62,18 @@ const ProfilePage: React.FC = () => { try { const profileData = { - user_id: user.id, - ...formData, - updated_at: new Date().toISOString() + ...formData }; - let result; - if (profile) { - // 更新现有档案 - result = await supabase - .from('user_profiles') - .update(profileData) - .eq('user_id', user.id) - .select() - .maybeSingle(); - } else { - // 创建新档案 - result = await supabase - .from('user_profiles') - .insert([{ - ...profileData, - created_at: new Date().toISOString() - }]) - .select() - .maybeSingle(); - } + const result = await localApi.profiles.update(profileData); if (result.error) { - throw result.error; + throw new Error(result.error.message); } - setProfile(result.data); + if (result.data && result.data.profile) { + setProfile(result.data.profile); + } toast.success('档案保存成功!'); } catch (error: any) { console.error('保存档案失败:', error); diff --git a/src/pages/WuxingAnalysisPage.tsx b/src/pages/WuxingAnalysisPage.tsx index 675b08f..4ff993d 100644 --- a/src/pages/WuxingAnalysisPage.tsx +++ b/src/pages/WuxingAnalysisPage.tsx @@ -3,7 +3,7 @@ import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Responsi import { Calendar, Clock, Zap, BarChart3, Sparkles, TrendingUp } from 'lucide-react'; import { Button } from '../components/ui/Button'; import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; -import { supabase } from '../lib/supabase'; +import { localApi } from '../lib/localApi'; import { useAuth } from '../contexts/AuthContext'; import { toast } from 'sonner'; @@ -59,7 +59,7 @@ const WuxingAnalysisPage: React.FC = () => { '水': '💧' }; - // 调用Supabase Edge Function进行五行分析 + // 进行五行分析 const fetchWuxingAnalysis = async () => { if (!birthDate) { toast.error('请选择您的出生日期'); @@ -70,18 +70,20 @@ const WuxingAnalysisPage: React.FC = () => { setError(null); try { - // 调用Supabase Edge Function - const { data, error } = await supabase.functions.invoke('bazi-wuxing-analysis', { + // 调用本地API + const response = await localApi.functions.invoke('bazi-wuxing-analysis', { body: { birthDate, birthTime } }); - if (error) throw error; + if (response.error) { + throw new Error(response.error.message); + } - if (data?.data) { - setAnalysisData(data.data); + if (response.data?.data) { + setAnalysisData(response.data.data); toast.success('五行分析完成!'); } else { throw new Error('分析结果为空'); diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest deleted file mode 100644 index 322987f..0000000 --- a/supabase/.temp/cli-latest +++ /dev/null @@ -1 +0,0 @@ -v2.34.3 \ No newline at end of file diff --git a/supabase/.temp/gotrue-version b/supabase/.temp/gotrue-version deleted file mode 100644 index debbfea..0000000 --- a/supabase/.temp/gotrue-version +++ /dev/null @@ -1 +0,0 @@ -v2.177.0 \ No newline at end of file diff --git a/supabase/.temp/pooler-url b/supabase/.temp/pooler-url deleted file mode 100644 index eec4062..0000000 --- a/supabase/.temp/pooler-url +++ /dev/null @@ -1 +0,0 @@ -postgresql://postgres.myiabzmycehtxxyybqfo:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres \ No newline at end of file diff --git a/supabase/.temp/postgres-version b/supabase/.temp/postgres-version deleted file mode 100644 index 59758ce..0000000 --- a/supabase/.temp/postgres-version +++ /dev/null @@ -1 +0,0 @@ -17.4.1.069 \ No newline at end of file diff --git a/supabase/.temp/project-ref b/supabase/.temp/project-ref deleted file mode 100644 index 54ac15c..0000000 --- a/supabase/.temp/project-ref +++ /dev/null @@ -1 +0,0 @@ -myiabzmycehtxxyybqfo \ No newline at end of file diff --git a/supabase/.temp/rest-version b/supabase/.temp/rest-version deleted file mode 100644 index c518e9a..0000000 --- a/supabase/.temp/rest-version +++ /dev/null @@ -1 +0,0 @@ -v13.0.4 \ No newline at end of file diff --git a/supabase/.temp/storage-version b/supabase/.temp/storage-version deleted file mode 100644 index 04f8825..0000000 --- a/supabase/.temp/storage-version +++ /dev/null @@ -1 +0,0 @@ -custom-metadata \ No newline at end of file diff --git a/test-analysis.js b/test-analysis.js new file mode 100644 index 0000000..e69de29