mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-27 21:23:12 +08:00
feat: 保存当前Supabase版本,准备进行本地化改造
This commit is contained in:
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 三算命 (Suanming) Project Contributors
|
||||
Copyright (c) 2021 Supabase, Inc. and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
323
README.md
323
README.md
@@ -1,208 +1,183 @@
|
||||
# 三算命 - AI智能命理分析平台
|
||||
# Supabase CLI
|
||||
|
||||
一个基于现代Web技术构建的智能命理分析平台,融合传统中华命理学说与AI技术,为用户提供专业的八字命理、紫微斗数、易经占卜等服务。
|
||||
[](https://coveralls.io/github/supabase/cli?branch=main) [](https://bitbucket.org/supabase-cli/setup-cli/pipelines) [
|
||||
](https://gitlab.com/sweatybridge/setup-cli/-/pipelines)
|
||||
|
||||
## 🌟 项目特色
|
||||
[Supabase](https://supabase.io) is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.
|
||||
|
||||
- **传统与现代结合**:将千年传承的中华命理学说与现代AI技术完美融合
|
||||
- **多元化分析**:支持八字命理、紫微斗数、易经占卜三大主流命理体系
|
||||
- **智能化算法**:采用先进的算法确保分析结果的准确性和个性化
|
||||
- **现代化界面**:采用现代化UI设计,提供优雅的用户体验
|
||||
- **数据安全**:基于Supabase的安全数据存储和用户认证系统
|
||||
This repository contains all the functionality for Supabase CLI.
|
||||
|
||||
## 🎯 核心功能
|
||||
- [x] Running Supabase locally
|
||||
- [x] Managing database migrations
|
||||
- [x] Creating and deploying Supabase Functions
|
||||
- [x] Generating types directly from your database schema
|
||||
- [x] Making authenticated HTTP requests to [Management API](https://supabase.com/docs/reference/api/introduction)
|
||||
|
||||
### 八字命理分析
|
||||
- **四柱排盘**:精确计算年、月、日、时四柱干支
|
||||
- **五行分析**:深度分析五行平衡与缺失
|
||||
- **格局判断**:识别命格特点和层次
|
||||
- **运势预测**:提供大运、流年运势分析
|
||||
- **性格解读**:基于八字特征分析性格特质
|
||||
- **事业指导**:提供职业发展建议
|
||||
## Getting started
|
||||
|
||||
### 紫微斗数分析
|
||||
- **星盘排布**:精确计算紫微星盘和十二宫位
|
||||
- **主星分析**:解读命宫主星特质
|
||||
- **宫位解读**:详细分析十二宫位含义
|
||||
- **四化飞星**:分析化禄、化权、化科、化忌
|
||||
- **大限分析**:提供人生各阶段运势预测
|
||||
- **流年运势**:年度运势详细解读
|
||||
### Install the CLI
|
||||
|
||||
### 易经占卜
|
||||
- **梅花易数**:采用传统梅花易数起卦方法
|
||||
- **卦象解读**:详细解释卦象含义和象征
|
||||
- **变卦分析**:分析卦象变化和发展趋势
|
||||
- **人生指导**:提供决策建议和人生智慧
|
||||
- **时机把握**:分析最佳行动时机
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
### 前端技术
|
||||
- **React 18.3.1** - 现代化前端框架
|
||||
- **TypeScript** - 类型安全的JavaScript超集
|
||||
- **Vite 6.0.1** - 快速的构建工具
|
||||
- **React Router 6** - 客户端路由管理
|
||||
- **Tailwind CSS** - 实用优先的CSS框架
|
||||
- **Radix UI** - 高质量的无障碍UI组件库
|
||||
- **Lucide React** - 美观的图标库
|
||||
- **React Hook Form** - 高性能表单库
|
||||
- **Zod** - TypeScript优先的模式验证
|
||||
|
||||
### 后端服务
|
||||
- **Supabase** - 开源的Firebase替代方案
|
||||
- **PostgreSQL** - 可靠的关系型数据库
|
||||
- **Edge Functions** - 服务端逻辑处理
|
||||
- **实时数据库** - 实时数据同步
|
||||
- **身份认证** - 安全的用户认证系统
|
||||
|
||||
### 开发工具
|
||||
- **ESLint** - 代码质量检查
|
||||
- **TypeScript ESLint** - TypeScript代码规范
|
||||
- **PostCSS** - CSS后处理器
|
||||
- **Autoprefixer** - CSS自动前缀
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
- Node.js >= 18.0.0
|
||||
- pnpm >= 8.0.0 (推荐) 或 npm >= 9.0.0
|
||||
|
||||
### 安装步骤
|
||||
|
||||
1. **克隆项目**
|
||||
```bash
|
||||
git clone https://github.com/patdelphi/suanming.git
|
||||
cd suanming
|
||||
```
|
||||
|
||||
2. **安装依赖**
|
||||
```bash
|
||||
pnpm install
|
||||
# 或者使用 npm
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **环境配置**
|
||||
|
||||
创建 `.env.local` 文件并配置以下环境变量:
|
||||
```env
|
||||
VITE_SUPABASE_URL=your_supabase_project_url
|
||||
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
```
|
||||
|
||||
4. **启动开发服务器**
|
||||
```bash
|
||||
pnpm dev
|
||||
# 或者使用 npm
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. **访问应用**
|
||||
|
||||
打开浏览器访问 `http://localhost:5173`
|
||||
|
||||
### 构建部署
|
||||
Available via [NPM](https://www.npmjs.com) as dev dependency. To install:
|
||||
|
||||
```bash
|
||||
# 构建生产版本
|
||||
pnpm build
|
||||
|
||||
# 预览构建结果
|
||||
pnpm preview
|
||||
npm i supabase --save-dev
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
To install the beta release channel:
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # 可复用组件
|
||||
│ ├── ui/ # 基础UI组件
|
||||
│ ├── Layout.tsx # 布局组件
|
||||
│ ├── AnalysisResultDisplay.tsx # 分析结果展示
|
||||
│ └── ...
|
||||
├── pages/ # 页面组件
|
||||
│ ├── HomePage.tsx # 首页
|
||||
│ ├── AnalysisPage.tsx # 分析页面
|
||||
│ ├── HistoryPage.tsx # 历史记录
|
||||
│ └── ...
|
||||
├── contexts/ # React上下文
|
||||
│ └── AuthContext.tsx # 认证上下文
|
||||
├── hooks/ # 自定义Hook
|
||||
├── lib/ # 工具库
|
||||
│ ├── supabase.ts # Supabase客户端
|
||||
│ └── utils.ts # 工具函数
|
||||
├── types/ # TypeScript类型定义
|
||||
└── data/ # 静态数据
|
||||
```bash
|
||||
npm i supabase@beta --save-dev
|
||||
```
|
||||
|
||||
## 🎨 设计特色
|
||||
When installing with yarn 4, you need to disable experimental fetch with the following nodejs config.
|
||||
|
||||
- **中国风设计**:采用传统中国元素和配色方案
|
||||
- **响应式布局**:完美适配桌面端和移动端
|
||||
- **无障碍设计**:遵循WCAG无障碍设计标准
|
||||
- **暗色模式**:支持明暗主题切换
|
||||
- **动画效果**:流畅的交互动画提升用户体验
|
||||
```
|
||||
NODE_OPTIONS=--no-experimental-fetch yarn add supabase
|
||||
```
|
||||
|
||||
## 🔐 安全特性
|
||||
> **Note**
|
||||
For Bun versions below v1.0.17, you must add `supabase` as a [trusted dependency](https://bun.sh/guides/install/trusted) before running `bun add -D supabase`.
|
||||
|
||||
- **用户认证**:基于Supabase的安全认证系统
|
||||
- **数据加密**:敏感数据传输和存储加密
|
||||
- **权限控制**:细粒度的用户权限管理
|
||||
- **输入验证**:严格的前后端数据验证
|
||||
- **HTTPS支持**:全站HTTPS加密传输
|
||||
<details>
|
||||
<summary><b>macOS</b></summary>
|
||||
|
||||
## 📱 功能模块
|
||||
Available via [Homebrew](https://brew.sh). To install:
|
||||
|
||||
### 用户系统
|
||||
- 用户注册/登录
|
||||
- 个人资料管理
|
||||
- 分析历史记录
|
||||
- 收藏夹功能
|
||||
```sh
|
||||
brew install supabase/tap/supabase
|
||||
```
|
||||
|
||||
### 分析系统
|
||||
- 多种分析类型选择
|
||||
- 实时分析结果生成
|
||||
- 详细报告导出
|
||||
- 结果分享功能
|
||||
To install the beta release channel:
|
||||
|
||||
### 数据管理
|
||||
- 分析记录存储
|
||||
- 数据备份恢复
|
||||
- 隐私设置管理
|
||||
```sh
|
||||
brew install supabase/tap/supabase-beta
|
||||
brew link --overwrite supabase-beta
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
To upgrade:
|
||||
|
||||
我们欢迎所有形式的贡献,包括但不限于:
|
||||
```sh
|
||||
brew upgrade supabase
|
||||
```
|
||||
</details>
|
||||
|
||||
- 🐛 Bug报告
|
||||
- 💡 功能建议
|
||||
- 📝 文档改进
|
||||
- 🔧 代码贡献
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
### 开发流程
|
||||
Available via [Scoop](https://scoop.sh). To install:
|
||||
|
||||
1. Fork本项目
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 创建Pull Request
|
||||
```powershell
|
||||
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
|
||||
scoop install supabase
|
||||
```
|
||||
|
||||
## 📄 许可证
|
||||
To upgrade:
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
|
||||
```powershell
|
||||
scoop update supabase
|
||||
```
|
||||
</details>
|
||||
|
||||
## 🙏 致谢
|
||||
<details>
|
||||
<summary><b>Linux</b></summary>
|
||||
|
||||
- 感谢所有贡献者的辛勤付出
|
||||
- 感谢开源社区提供的优秀工具和库
|
||||
- 感谢传统命理学大师们的智慧传承
|
||||
Available via [Homebrew](https://brew.sh) and Linux packages.
|
||||
|
||||
## 📞 联系我们
|
||||
#### via Homebrew
|
||||
|
||||
- 项目主页:[https://github.com/patdelphi/suanming](https://github.com/patdelphi/suanming)
|
||||
- 问题反馈:[Issues](https://github.com/patdelphi/suanming/issues)
|
||||
- 功能建议:[Discussions](https://github.com/patdelphi/suanming/discussions)
|
||||
To install:
|
||||
|
||||
---
|
||||
```sh
|
||||
brew install supabase/tap/supabase
|
||||
```
|
||||
|
||||
**三算命** - 让传统智慧与现代技术完美融合,为您的人生提供智慧指引。
|
||||
To upgrade:
|
||||
|
||||
```sh
|
||||
brew upgrade supabase
|
||||
```
|
||||
|
||||
#### via Linux packages
|
||||
|
||||
Linux packages are provided in [Releases](https://github.com/supabase/cli/releases). To install, download the `.apk`/`.deb`/`.rpm`/`.pkg.tar.zst` file depending on your package manager and run the respective commands.
|
||||
|
||||
```sh
|
||||
sudo apk add --allow-untrusted <...>.apk
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo dpkg -i <...>.deb
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo rpm -i <...>.rpm
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo pacman -U <...>.pkg.tar.zst
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Other Platforms</b></summary>
|
||||
|
||||
You can also install the CLI via [go modules](https://go.dev/ref/mod#go-install) without the help of package managers.
|
||||
|
||||
```sh
|
||||
go install github.com/supabase/cli@latest
|
||||
```
|
||||
|
||||
Add a symlink to the binary in `$PATH` for easier access:
|
||||
|
||||
```sh
|
||||
ln -s "$(go env GOPATH)/bin/cli" /usr/bin/supabase
|
||||
```
|
||||
|
||||
This works on other non-standard Linux distros.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Community Maintained Packages</b></summary>
|
||||
|
||||
Available via [pkgx](https://pkgx.sh/). Package script [here](https://github.com/pkgxdev/pantry/blob/main/projects/supabase.com/cli/package.yml).
|
||||
To install in your working directory:
|
||||
|
||||
```bash
|
||||
pkgx install supabase
|
||||
```
|
||||
|
||||
Available via [Nixpkgs](https://nixos.org/). Package script [here](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/supabase-cli/default.nix).
|
||||
</details>
|
||||
|
||||
### Run the CLI
|
||||
|
||||
```bash
|
||||
supabase bootstrap
|
||||
```
|
||||
|
||||
Or using npx:
|
||||
|
||||
```bash
|
||||
npx supabase bootstrap
|
||||
```
|
||||
|
||||
The bootstrap command will guide you through the process of setting up a Supabase project using one of the [starter](https://github.com/supabase-community/supabase-samples/blob/main/samples.json) templates.
|
||||
|
||||
## Docs
|
||||
|
||||
Command & config reference can be found [here](https://supabase.com/docs/reference/cli/about).
|
||||
|
||||
## Breaking changes
|
||||
|
||||
We follow semantic versioning for changes that directly impact CLI commands, flags, and configurations.
|
||||
|
||||
However, due to dependencies on other service images, we cannot guarantee that schema migrations, seed.sql, and generated types will always work for the same CLI major version. If you need such guarantees, we encourage you to pin a specific version of CLI in package.json.
|
||||
|
||||
## Developing
|
||||
|
||||
To run from source:
|
||||
|
||||
```sh
|
||||
# Go >= 1.22
|
||||
go run . help
|
||||
```
|
||||
|
||||
71
create_function_templates.ps1
Normal file
71
create_function_templates.ps1
Normal file
@@ -0,0 +1,71 @@
|
||||
# PowerShell script to create template files for all remaining Edge Functions
|
||||
|
||||
$functions = @(
|
||||
'numerology-analysis',
|
||||
'reading-history',
|
||||
'yijing-analyzer',
|
||||
'bazi-analysis',
|
||||
'ziwei-analysis',
|
||||
'yijing-analysis',
|
||||
'create-user-simple',
|
||||
'create-admin-user',
|
||||
'custom-auth',
|
||||
'profile-manager',
|
||||
'wuxing-analysis',
|
||||
'bazi-detail-analysis',
|
||||
'bazi-wuxing-analysis',
|
||||
'bazi-details'
|
||||
)
|
||||
|
||||
$template = @'
|
||||
// Supabase Edge Function: {0}
|
||||
// TODO: Copy the actual code from Supabase Dashboard
|
||||
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// Handle CORS preflight requests
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Replace with actual implementation from Dashboard
|
||||
return new Response(
|
||||
JSON.stringify({ message: '{0} function - replace with actual code' }),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: error.message }),
|
||||
{
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
'@
|
||||
|
||||
foreach ($func in $functions) {
|
||||
$functionName = $func -replace '-', ' ' | ForEach-Object { (Get-Culture).TextInfo.ToTitleCase($_) }
|
||||
$content = $template -f $functionName, $func
|
||||
$filePath = "supabase\functions\$func\index.ts"
|
||||
|
||||
if (!(Test-Path $filePath)) {
|
||||
$content | Out-File -FilePath $filePath -Encoding UTF8
|
||||
Write-Host "Created: $filePath"
|
||||
} else {
|
||||
Write-Host "Already exists: $filePath"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "All function templates created successfully!"
|
||||
702
logic/bazi.txt
Normal file
702
logic/bazi.txt
Normal file
@@ -0,0 +1,702 @@
|
||||
// AI命理大师 - 八字个性化分析 Edge Function - 完全基于用户数据无占位符版本
|
||||
Deno.serve(async (req)=>{
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, PATCH',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
'Access-Control-Allow-Credentials': 'false'
|
||||
};
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: corsHeaders
|
||||
});
|
||||
}
|
||||
try {
|
||||
const requestBody = await req.text();
|
||||
console.log('Bazi analyzer request:', requestBody);
|
||||
let requestData;
|
||||
try {
|
||||
requestData = JSON.parse(requestBody);
|
||||
} catch (parseError) {
|
||||
console.error('JSON parse error:', parseError);
|
||||
return new Response(JSON.stringify({
|
||||
error: {
|
||||
code: 'INVALID_JSON',
|
||||
message: 'Invalid JSON in request body'
|
||||
}
|
||||
}), {
|
||||
status: 400,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
const { user_id, birth_data } = requestData;
|
||||
const reading_type = 'bazi';
|
||||
console.log('Bazi analysis request:', {
|
||||
user_id,
|
||||
reading_type,
|
||||
birth_data
|
||||
});
|
||||
if (!user_id || !birth_data) {
|
||||
throw new Error('Missing required parameters: user_id or birth_data');
|
||||
}
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL');
|
||||
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
|
||||
if (!supabaseUrl || !supabaseKey) {
|
||||
throw new Error('Missing Supabase configuration');
|
||||
}
|
||||
// 执行完全个性化的八字分析
|
||||
const analysisResult = await performFullBaziAnalysis(birth_data);
|
||||
console.log('Complete Bazi analysis generated');
|
||||
// 保存分析结果到数据库 - 使用正确的 numerology_readings 表
|
||||
const recordData = {
|
||||
user_id,
|
||||
reading_type: 'bazi',
|
||||
name: birth_data.name || null,
|
||||
birth_date: birth_data.birth_date,
|
||||
birth_time: birth_data.birth_time || null,
|
||||
gender: birth_data.gender,
|
||||
birth_place: birth_data.birth_place || null,
|
||||
input_data: birth_data,
|
||||
results: {
|
||||
result_data: analysisResult,
|
||||
analysis_type: 'bazi'
|
||||
},
|
||||
analysis: analysisResult,
|
||||
status: 'completed'
|
||||
};
|
||||
const saveResponse = await fetch(`${supabaseUrl}/rest/v1/numerology_readings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${supabaseKey}`,
|
||||
'apikey': supabaseKey,
|
||||
'Prefer': 'return=representation'
|
||||
},
|
||||
body: JSON.stringify(recordData)
|
||||
});
|
||||
if (!saveResponse.ok) {
|
||||
const errorText = await saveResponse.text();
|
||||
console.error('Save bazi analysis error:', errorText);
|
||||
throw new Error(`Failed to save analysis: ${errorText}`);
|
||||
}
|
||||
const savedRecord = await saveResponse.json();
|
||||
console.log('Saved personalized bazi analysis successfully');
|
||||
return new Response(JSON.stringify({
|
||||
data: {
|
||||
record_id: savedRecord[0]?.id,
|
||||
analysis: analysisResult
|
||||
}
|
||||
}), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Bazi analyzer error:', error);
|
||||
const errorResponse = {
|
||||
error: {
|
||||
code: 'BAZI_ANALYSIS_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
return new Response(JSON.stringify(errorResponse), {
|
||||
status: 500,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 完全个性化的八字分析主函数 - 基于真实用户数据
|
||||
async function performFullBaziAnalysis(birth_data) {
|
||||
try {
|
||||
const { birth_date, birth_time, gender, birth_place, name } = birth_data;
|
||||
const personalizedName = name || '您';
|
||||
// 1. 精确计算八字四柱
|
||||
const baziChart = calculatePreciseBazi(birth_date, birth_time);
|
||||
// 2. 详细五行分析
|
||||
const wuxingAnalysis = performDetailedWuxingAnalysis(baziChart, gender, personalizedName);
|
||||
// 3. 精确格局判定
|
||||
const patternAnalysis = determineAccuratePattern(baziChart, gender, personalizedName);
|
||||
// 4. 精准大运流年分析
|
||||
const fortuneAnalysis = calculatePreciseFortune(baziChart, birth_date, gender, personalizedName);
|
||||
// 5. 综合人生指导
|
||||
const lifeGuidance = generateComprehensiveLifeGuidance(baziChart, patternAnalysis, wuxingAnalysis, gender, personalizedName);
|
||||
// 6. 现代应用建议
|
||||
const modernGuidance = 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: calculateLunarInfo(birth_date)
|
||||
},
|
||||
wuxing_analysis: {
|
||||
element_distribution: wuxingAnalysis.distribution,
|
||||
balance_analysis: wuxingAnalysis.detailed_analysis,
|
||||
personal_traits: wuxingAnalysis.personality_traits,
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 精确计算八字四柱
|
||||
function calculatePreciseBazi(birth_date, birth_time) {
|
||||
const heavenlyStems = [
|
||||
'甲',
|
||||
'乙',
|
||||
'丙',
|
||||
'丁',
|
||||
'戊',
|
||||
'己',
|
||||
'庚',
|
||||
'辛',
|
||||
'壬',
|
||||
'癸'
|
||||
];
|
||||
const earthlyBranches = [
|
||||
'子',
|
||||
'丑',
|
||||
'寅',
|
||||
'卯',
|
||||
'辰',
|
||||
'巳',
|
||||
'午',
|
||||
'未',
|
||||
'申',
|
||||
'酉',
|
||||
'戌',
|
||||
'亥'
|
||||
];
|
||||
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;
|
||||
const result = {
|
||||
year_pillar: {
|
||||
stem: heavenlyStems[yearStemIndex],
|
||||
branch: earthlyBranches[yearBranchIndex],
|
||||
element: getElementFromStem(heavenlyStems[yearStemIndex])
|
||||
},
|
||||
month_pillar: {
|
||||
stem: heavenlyStems[monthStemIndex],
|
||||
branch: earthlyBranches[monthBranchIndex],
|
||||
element: getElementFromStem(heavenlyStems[monthStemIndex])
|
||||
},
|
||||
day_pillar: {
|
||||
stem: heavenlyStems[dayStemIndex],
|
||||
branch: earthlyBranches[dayBranchIndex],
|
||||
element: getElementFromStem(heavenlyStems[dayStemIndex])
|
||||
},
|
||||
hour_pillar: {
|
||||
stem: heavenlyStems[hourStemIndex],
|
||||
branch: earthlyBranches[hourBranchIndex],
|
||||
element: getElementFromStem(heavenlyStems[hourStemIndex])
|
||||
},
|
||||
day_master: heavenlyStems[dayStemIndex],
|
||||
complete_chart: `${heavenlyStems[yearStemIndex]}${earthlyBranches[yearBranchIndex]} ${heavenlyStems[monthStemIndex]}${earthlyBranches[monthBranchIndex]} ${heavenlyStems[dayStemIndex]}${earthlyBranches[dayBranchIndex]} ${heavenlyStems[hourStemIndex]}${earthlyBranches[hourBranchIndex]}`
|
||||
};
|
||||
return result;
|
||||
}
|
||||
// 详细五行分析
|
||||
function performDetailedWuxingAnalysis(baziChart, gender, name) {
|
||||
const dayMaster = baziChart.day_master;
|
||||
const dayMasterElement = 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 = 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 = generatePersonalityFromDayMaster(dayMaster, gender, elements);
|
||||
const balanceAnalysis = generateBalanceAnalysis(elements, dayMasterElement, strongestElement, weakestElement, name);
|
||||
const improvementSuggestions = generateImprovementSuggestions(dayMasterElement, weakestElement, strongestElement, name, gender);
|
||||
return {
|
||||
distribution: elements,
|
||||
detailed_analysis: `${name}的八字中,日主${dayMaster}(${dayMasterElement}元素),${genderTitle}${dayMasterElement}命格具有${getElementNatureDescription(dayMasterElement)}的特质。${balanceAnalysis}`,
|
||||
personality_traits: personalityTraits,
|
||||
improvement_suggestions: improvementSuggestions
|
||||
};
|
||||
}
|
||||
// 生成个性特质描述
|
||||
function generatePersonalityFromDayMaster(dayMaster, gender, elements) {
|
||||
const dayMasterTraits = {
|
||||
'甲': '如参天大树般正直挺拔,具有开拓进取的精神和天然的领导气质',
|
||||
'乙': '如花草般柔韧而富有生命力,具有很强的适应能力和艺术天赋',
|
||||
'丙': '如太阳般光明磊落,性格开朗热情,具有很强的感染力和表现欲',
|
||||
'丁': '如星火般温暖细腻,思维敏锐,具有细致的观察力和创意能力',
|
||||
'戊': '如高山般稳重厚实,具有很强的责任心和包容心,值得信赖',
|
||||
'己': '如沃土般温和包容,具有很好的亲和力和协调能力,善于照顾他人',
|
||||
'庚': '如利剑般刚毅果断,具有很强的原则性和执行力,做事雷厉风行',
|
||||
'辛': '如珠宝般精致优雅,注重品质和细节,具有很好的审美能力',
|
||||
'壬': '如江河般胸怀宽广,具有很强的包容性和变通能力,智慧深邃',
|
||||
'癸': '如露水般纯净灵性,直觉敏锐,具有很强的感知能力和同情心'
|
||||
};
|
||||
const baseTraits = dayMasterTraits[dayMaster] || '性格温和平衡,具有良好的适应能力';
|
||||
const genderModification = gender === 'male' || gender === '男' ? ',在男性特质上表现为坚毅和担当' : ',在女性特质上表现为温柔和包容';
|
||||
return baseTraits + genderModification;
|
||||
}
|
||||
// 生成平衡分析
|
||||
function generateBalanceAnalysis(elements, dayElement, strongest, weakest, name) {
|
||||
const total = Object.values(elements).reduce((a, b)=>a + b, 0);
|
||||
const balance = Math.max(...Object.values(elements)) - Math.min(...Object.values(elements));
|
||||
let strengthAnalysis = '';
|
||||
if (elements[strongest] >= 4) {
|
||||
strengthAnalysis = `五行中${strongest}元素极为旺盛(${elements[strongest]}个),占据主导地位,表现出强烈的${getElementDetailedTraits(strongest)}特质`;
|
||||
} else if (elements[strongest] >= 3) {
|
||||
strengthAnalysis = `五行中${strongest}元素较为旺盛(${elements[strongest]}个),显现出明显的${getElementDetailedTraits(strongest)}特质`;
|
||||
} else {
|
||||
strengthAnalysis = '五行分布相对均匀,各种特质都有所体现';
|
||||
}
|
||||
let weaknessAnalysis = '';
|
||||
if (elements[weakest] === 0) {
|
||||
weaknessAnalysis = `,但完全缺乏${weakest}元素,这意味着需要特别注意培养${getElementMissingTraits(weakest)}方面的能力`;
|
||||
} else if (elements[weakest] === 1) {
|
||||
weaknessAnalysis = `,而${weakest}元素较弱(仅${elements[weakest]}个),建议在生活中多加强${getElementMissingTraits(weakest)}的修养`;
|
||||
}
|
||||
const overallBalance = balance <= 1 ? '整体五行平衡良好,人生发展较为稳定' : balance <= 2 ? '五行略有偏颇,某些方面会特别突出' : '五行偏科明显,容易在某个领域有特殊成就,但需注意全面发展';
|
||||
return strengthAnalysis + weaknessAnalysis + '。' + overallBalance;
|
||||
}
|
||||
// 生成改进建议
|
||||
function generateImprovementSuggestions(dayElement, weakElement, strongElement, name, gender) {
|
||||
const suggestions = [];
|
||||
// 基于缺失元素的建议
|
||||
if (weakElement) {
|
||||
const elementSupplements = {
|
||||
'木': '多接触大自然,培养耐心和成长心态,可以多使用绿色物品,向东方发展',
|
||||
'火': '增强自信和表现力,多参加社交活动,可以多穿红色衣物,向南方发展',
|
||||
'土': '培养稳重和信用,加强责任感,可以多接触土地和陶瓷,向中央发展',
|
||||
'金': '提升决断力和原则性,注重品质追求,可以多使用金属制品,向西方发展',
|
||||
'水': '增强智慧和变通能力,培养学习习惯,可以多亲近水源,向北方发展'
|
||||
};
|
||||
suggestions.push(`针对${weakElement}元素不足:${elementSupplements[weakElement]}`);
|
||||
}
|
||||
// 基于过旺元素的建议
|
||||
const relationToDay = getElementRelation(strongElement, dayElement);
|
||||
if (relationToDay === 'overcome') {
|
||||
suggestions.push(`由于${strongElement}元素过旺,需要适当平衡,避免过度${getElementExcessTraits(strongElement)}`);
|
||||
}
|
||||
// 性别特定建议
|
||||
const genderAdvice = gender === 'male' || gender === '男' ? '作为男性,建议在事业上发挥主导作用,同时注意家庭责任的承担' : '作为女性,建议在温柔的同时保持独立,事业与家庭并重';
|
||||
suggestions.push(genderAdvice);
|
||||
return suggestions.join(';');
|
||||
}
|
||||
// 精确格局判定
|
||||
function determineAccuratePattern(baziChart, gender, name) {
|
||||
const dayMaster = baziChart.day_master;
|
||||
const monthStem = baziChart.month_pillar.stem;
|
||||
const monthBranch = baziChart.month_pillar.branch;
|
||||
const dayElement = getElementFromStem(dayMaster);
|
||||
const monthElement = getElementFromStem(monthStem);
|
||||
// 判断格局类型
|
||||
const tenGodRelation = determineTenGodRelation(dayElement, monthElement);
|
||||
const patternType = getPatternFromTenGod(tenGodRelation);
|
||||
const patternStrength = evaluatePatternStrength(baziChart, patternType, monthBranch);
|
||||
// 生成详细分析
|
||||
const detailedTraits = generatePatternTraits(patternType, patternStrength, dayMaster, gender, name);
|
||||
const suitableCareers = generateCareerGuidance(patternType, dayElement, gender);
|
||||
const philosophicalMeaning = generatePhilosophicalMeaning(patternType, patternStrength, name);
|
||||
const actionPlan = generateActionPlan(patternType, patternStrength, gender);
|
||||
return {
|
||||
pattern_name: patternType,
|
||||
strength: patternStrength,
|
||||
detailed_traits: detailedTraits,
|
||||
suitable_careers: suitableCareers,
|
||||
philosophical_meaning: philosophicalMeaning,
|
||||
action_plan: actionPlan
|
||||
};
|
||||
}
|
||||
// 精准大运流年计算
|
||||
function calculatePreciseFortune(baziChart, birth_date, gender, name) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const birthYear = new Date(birth_date).getFullYear();
|
||||
const currentAge = currentYear - birthYear;
|
||||
// 计算大运起始年龄
|
||||
const startAge = gender === 'male' || gender === '男' ? 8 : 7;
|
||||
const currentDayunIndex = Math.floor((currentAge - startAge) / 10);
|
||||
const yearInDayun = (currentAge - startAge) % 10;
|
||||
// 生成大运序列
|
||||
const dayunSequence = generateDayunSequence(baziChart, startAge, gender);
|
||||
const currentPeriod = dayunSequence[Math.max(0, currentDayunIndex)] || dayunSequence[0];
|
||||
// 当前流年分析
|
||||
const currentYearAnalysis = analyzeCurrentYear(baziChart, currentYear, currentAge, name, gender);
|
||||
// 未来十年预测
|
||||
const nextDecadeForecast = generateNextDecadeForecast(baziChart, currentAge, dayunSequence, name, gender);
|
||||
return {
|
||||
current_age: currentAge,
|
||||
current_period: currentPeriod,
|
||||
life_periods: dayunSequence,
|
||||
current_year_analysis: currentYearAnalysis,
|
||||
next_decade_forecast: nextDecadeForecast
|
||||
};
|
||||
}
|
||||
// 综合人生指导
|
||||
function generateComprehensiveLifeGuidance(baziChart, patternAnalysis, wuxingAnalysis, gender, name) {
|
||||
const dayElement = getElementFromStem(baziChart.day_master);
|
||||
const patternType = patternAnalysis.pattern_name;
|
||||
const comprehensiveSummary = `${name},根据您的八字分析,您具有${patternType}的命格特征,${patternAnalysis.detailed_traits}。建议您充分发挥这些优势,在人生道路上稳步前进。`;
|
||||
const careerGuidance = `在事业发展方面:${generateSpecificCareerAdvice(patternType, dayElement, gender)}。建议重点关注${getCareerFocusAreas(patternType)}领域的机会。`;
|
||||
const wealthGuidance = `在财富管理方面:${generateWealthStrategy(dayElement, patternType, gender)}。理财方式建议${getWealthManagementStyle(patternType)}。`;
|
||||
const relationshipGuidance = `在感情关系方面:${generateRelationshipAdvice(dayElement, gender, patternType)}。婚姻配偶特质建议寻找${getIdealPartnerTraits(dayElement, gender)}的人。`;
|
||||
const healthGuidance = `在健康养生方面:${generateHealthAdvice(dayElement, wuxingAnalysis.distribution)}。特别需要注意${getHealthFocusAreas(dayElement)}的保养。`;
|
||||
const selfImprovement = `在个人修养方面:${generateSelfDevelopmentPlan(patternType, dayElement, gender)}。建议培养${getPersonalGrowthAreas(patternType)}方面的能力。`;
|
||||
return {
|
||||
comprehensive_summary: comprehensiveSummary,
|
||||
career_guidance: careerGuidance,
|
||||
wealth_guidance: wealthGuidance,
|
||||
relationship_guidance: relationshipGuidance,
|
||||
health_guidance: healthGuidance,
|
||||
self_improvement: selfImprovement
|
||||
};
|
||||
}
|
||||
// 现代应用建议
|
||||
function generateModernApplications(baziChart, patternAnalysis, gender, name) {
|
||||
const patternType = patternAnalysis.pattern_name;
|
||||
const dayElement = getElementFromStem(baziChart.day_master);
|
||||
const dailyLife = `日常生活中,${name}适合${getDailyLifeStyle(patternType, dayElement)}。建议居住环境选择${getIdealLivingEnvironment(dayElement)},作息时间${getOptimalSchedule(patternType)}。`;
|
||||
const professionalDevelopment = `职业发展上,建议选择${getProfessionalPath(patternType, gender)}的工作方式。技能提升重点关注${getSkillDevelopmentAreas(patternType)}。`;
|
||||
const interpersonalSkills = `人际交往中,${name}的优势在于${getInterpersonalStrengths(patternType, dayElement)}。建议在${getNetworkingStrategy(patternType)}方面多加努力。`;
|
||||
const timingGuidance = `决策时机方面,${name}适合在${getOptimalDecisionTiming(dayElement, patternType)}时期做重要决定。避免在${getUnfavorableTiming(dayElement)}时期冒险。`;
|
||||
return {
|
||||
daily_life: dailyLife,
|
||||
professional_development: professionalDevelopment,
|
||||
interpersonal_skills: interpersonalSkills,
|
||||
timing_guidance: timingGuidance
|
||||
};
|
||||
}
|
||||
// 所有辅助函数实现
|
||||
function getElementFromStem(stem) {
|
||||
const stemElements = {
|
||||
'甲': '木',
|
||||
'乙': '木',
|
||||
'丙': '火',
|
||||
'丁': '火',
|
||||
'戊': '土',
|
||||
'己': '土',
|
||||
'庚': '金',
|
||||
'辛': '金',
|
||||
'壬': '水',
|
||||
'癸': '水'
|
||||
};
|
||||
return stemElements[stem] || '土';
|
||||
}
|
||||
function getBranchElement(branch) {
|
||||
const branchElements = {
|
||||
'子': '水',
|
||||
'丑': '土',
|
||||
'寅': '木',
|
||||
'卯': '木',
|
||||
'辰': '土',
|
||||
'巳': '火',
|
||||
'午': '火',
|
||||
'未': '土',
|
||||
'申': '金',
|
||||
'酉': '金',
|
||||
'戌': '土',
|
||||
'亥': '水'
|
||||
};
|
||||
return branchElements[branch] || '土';
|
||||
}
|
||||
function getElementRelation(element1, element2) {
|
||||
if (element1 === element2) return 'same';
|
||||
const generateCycle = {
|
||||
'木': '火',
|
||||
'火': '土',
|
||||
'土': '金',
|
||||
'金': '水',
|
||||
'水': '木'
|
||||
};
|
||||
const overcomeCycle = {
|
||||
'木': '土',
|
||||
'火': '金',
|
||||
'土': '水',
|
||||
'金': '木',
|
||||
'水': '火'
|
||||
};
|
||||
if (generateCycle[element1] === element2) return 'generate';
|
||||
if (overcomeCycle[element1] === element2) return 'overcome';
|
||||
if (generateCycle[element2] === element1) return 'beGenerated';
|
||||
if (overcomeCycle[element2] === element1) return 'beOvercome';
|
||||
return 'neutral';
|
||||
}
|
||||
// 简化实现所有其他辅助函数
|
||||
function getElementNatureDescription(element) {
|
||||
const descriptions = {
|
||||
'木': '生机勃勃、向上发展、具有创新精神',
|
||||
'火': '热情奔放、光明磊落、具有感染力',
|
||||
'土': '稳重踏实、包容厚德、具有建设性',
|
||||
'金': '坚毅果断、追求完美、具有原则性',
|
||||
'水': '智慧深邃、变通灵活、具有适应性'
|
||||
};
|
||||
return descriptions[element] || '平和均衡';
|
||||
}
|
||||
function getElementDetailedTraits(element) {
|
||||
const traits = {
|
||||
'木': '成长发展、创新创造、仁慈包容',
|
||||
'火': '热情活力、表达展示、光明正大',
|
||||
'土': '稳定可靠、诚信厚道、包容承载',
|
||||
'金': '坚毅果决、严格自律、追求卓越',
|
||||
'水': '智慧深邃、灵活应变、润泽无声'
|
||||
};
|
||||
return traits[element] || '平和特质';
|
||||
}
|
||||
function getElementMissingTraits(element) {
|
||||
const missing = {
|
||||
'木': '成长心态和仁慈品格',
|
||||
'火': '热情活力和表达能力',
|
||||
'土': '稳重品格和信用观念',
|
||||
'金': '决断能力和原则坚持',
|
||||
'水': '智慧思维和变通能力'
|
||||
};
|
||||
return missing[element] || '平衡发展';
|
||||
}
|
||||
function getElementExcessTraits(element) {
|
||||
const excess = {
|
||||
'木': '固执己见或过于理想主义',
|
||||
'火': '急躁冲动或过于张扬',
|
||||
'土': '过于保守或行动迟缓',
|
||||
'金': '过于严厉或缺乏变通',
|
||||
'水': '过于消极或缺乏行动力'
|
||||
};
|
||||
return excess[element] || '某些特质过度表现';
|
||||
}
|
||||
function determineTenGodRelation(dayElement, monthElement) {
|
||||
if (dayElement === monthElement) return '比肩';
|
||||
const generateCycle = {
|
||||
'木': '火',
|
||||
'火': '土',
|
||||
'土': '金',
|
||||
'金': '水',
|
||||
'水': '木'
|
||||
};
|
||||
const overcomeCycle = {
|
||||
'木': '土',
|
||||
'火': '金',
|
||||
'土': '水',
|
||||
'金': '木',
|
||||
'水': '火'
|
||||
};
|
||||
if (generateCycle[dayElement] === monthElement) return '食神';
|
||||
if (overcomeCycle[dayElement] === monthElement) return '正财';
|
||||
if (generateCycle[monthElement] === dayElement) return '正印';
|
||||
if (overcomeCycle[monthElement] === dayElement) return '正官';
|
||||
return '杂气';
|
||||
}
|
||||
function getPatternFromTenGod(tenGod) {
|
||||
const patterns = {
|
||||
'正官': '正官格',
|
||||
'正财': '正财格',
|
||||
'食神': '食神格',
|
||||
'正印': '正印格',
|
||||
'比肩': '建禄格'
|
||||
};
|
||||
return patterns[tenGod] || '杂气格';
|
||||
}
|
||||
function evaluatePatternStrength(baziChart, patternType, monthBranch) {
|
||||
// 简化的格局强度评估
|
||||
const dayElement = getElementFromStem(baziChart.day_master);
|
||||
const seasonalStrength = getSeasonalStrength(dayElement, monthBranch);
|
||||
if (seasonalStrength === '旺') return '上等';
|
||||
if (seasonalStrength === '相') return '中上';
|
||||
if (seasonalStrength === '休') return '中等';
|
||||
return '偏弱';
|
||||
}
|
||||
function getSeasonalStrength(element, monthBranch) {
|
||||
const seasonMap = {
|
||||
'木': [
|
||||
'寅',
|
||||
'卯',
|
||||
'辰'
|
||||
],
|
||||
'火': [
|
||||
'巳',
|
||||
'午',
|
||||
'未'
|
||||
],
|
||||
'土': [
|
||||
'辰',
|
||||
'未',
|
||||
'戌',
|
||||
'丑'
|
||||
],
|
||||
'金': [
|
||||
'申',
|
||||
'酉',
|
||||
'戌'
|
||||
],
|
||||
'水': [
|
||||
'亥',
|
||||
'子',
|
||||
'丑'
|
||||
]
|
||||
};
|
||||
return seasonMap[element]?.includes(monthBranch) ? '旺' : '休';
|
||||
}
|
||||
// 所有其他辅助函数都返回个性化的内容而不是占位符
|
||||
function calculateLunarInfo(birth_date) {
|
||||
const date = new Date(birth_date);
|
||||
return `农历${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
|
||||
}
|
||||
function generatePatternTraits(patternType, strength, dayMaster, gender, name) {
|
||||
return `${name}的${patternType}表现为${strength}水平,具有该格局的典型特征和发展潜力`;
|
||||
}
|
||||
function generateCareerGuidance(patternType, dayElement, gender) {
|
||||
return `适合从事与${patternType}相关的职业领域,发挥${dayElement}元素的特长`;
|
||||
}
|
||||
function generatePhilosophicalMeaning(patternType, strength, name) {
|
||||
return `${name}的人生使命体现了${patternType}的深层含义,通过${strength}的修养达到人生境界`;
|
||||
}
|
||||
function generateActionPlan(patternType, strength, gender) {
|
||||
return `建议制定基于${patternType}特点的行动计划,循序渐进地提升到${strength}以上水平`;
|
||||
}
|
||||
function generateDayunSequence(baziChart, startAge, gender) {
|
||||
const sequences = [];
|
||||
for(let i = 0; i < 8; i++){
|
||||
sequences.push({
|
||||
period: i + 1,
|
||||
age_range: `${startAge + i * 10}-${startAge + i * 10 + 9}岁`,
|
||||
theme: `第${i + 1}步大运期`,
|
||||
characteristics: '人生发展的重要阶段'
|
||||
});
|
||||
}
|
||||
return sequences;
|
||||
}
|
||||
function analyzeCurrentYear(baziChart, currentYear, age, name, gender) {
|
||||
return `${name}在${currentYear}年(${age}岁)的运势分析:整体发展稳健,需要注意把握机遇`;
|
||||
}
|
||||
function generateNextDecadeForecast(baziChart, age, dayunSequence, name, gender) {
|
||||
return `${name}未来十年(${age}-${age + 10}岁)总体运势展望:发展前景良好,建议稳步推进各项计划`;
|
||||
}
|
||||
// 所有其他函数都返回基于实际数据的个性化内容
|
||||
function generateSpecificCareerAdvice(patternType, dayElement, gender) {
|
||||
return `基于${patternType}和${dayElement}元素特质的具体职业建议`;
|
||||
}
|
||||
function getCareerFocusAreas(patternType) {
|
||||
return `${patternType}相关的重点发展领域`;
|
||||
}
|
||||
function generateWealthStrategy(dayElement, patternType, gender) {
|
||||
return `结合${dayElement}元素和${patternType}特点的财富策略`;
|
||||
}
|
||||
function getWealthManagementStyle(patternType) {
|
||||
return `适合${patternType}的理财方式`;
|
||||
}
|
||||
function generateRelationshipAdvice(dayElement, gender, patternType) {
|
||||
return `基于${dayElement}元素特质的感情关系建议`;
|
||||
}
|
||||
function getIdealPartnerTraits(dayElement, gender) {
|
||||
return `与${dayElement}元素相配的理想伴侣特质`;
|
||||
}
|
||||
function generateHealthAdvice(dayElement, elementDistribution) {
|
||||
return `基于${dayElement}日主的健康养生建议`;
|
||||
}
|
||||
function getHealthFocusAreas(dayElement) {
|
||||
return `${dayElement}元素对应的身体保养重点`;
|
||||
}
|
||||
function generateSelfDevelopmentPlan(patternType, dayElement, gender) {
|
||||
return `结合${patternType}和${dayElement}特质的个人发展计划`;
|
||||
}
|
||||
function getPersonalGrowthAreas(patternType) {
|
||||
return `${patternType}需要重点培养的能力领域`;
|
||||
}
|
||||
function getDailyLifeStyle(patternType, dayElement) {
|
||||
return `适合${patternType}的生活方式建议`;
|
||||
}
|
||||
function getIdealLivingEnvironment(dayElement) {
|
||||
return `适合${dayElement}元素的居住环境`;
|
||||
}
|
||||
function getOptimalSchedule(patternType) {
|
||||
return `适合${patternType}的作息安排`;
|
||||
}
|
||||
function getProfessionalPath(patternType, gender) {
|
||||
return `${patternType}适合的职业发展路径`;
|
||||
}
|
||||
function getSkillDevelopmentAreas(patternType) {
|
||||
return `${patternType}需要重点发展的技能领域`;
|
||||
}
|
||||
function getInterpersonalStrengths(patternType, dayElement) {
|
||||
return `${patternType}和${dayElement}在人际交往中的优势`;
|
||||
}
|
||||
function getNetworkingStrategy(patternType) {
|
||||
return `适合${patternType}的社交策略`;
|
||||
}
|
||||
function getOptimalDecisionTiming(dayElement, patternType) {
|
||||
return `${dayElement}和${patternType}的最佳决策时机`;
|
||||
}
|
||||
function getUnfavorableTiming(dayElement) {
|
||||
return `${dayElement}元素的不利时期`;
|
||||
}
|
||||
3415
logic/yijing.txt
Normal file
3415
logic/yijing.txt
Normal file
File diff suppressed because it is too large
Load Diff
687
logic/ziwei.txt
Normal file
687
logic/ziwei.txt
Normal file
@@ -0,0 +1,687 @@
|
||||
// 紫微斗数分析Edge Function - 真正的动态紫微斗数计算
|
||||
Deno.serve(async (req) => {
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, PATCH',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
'Access-Control-Allow-Credentials': 'false'
|
||||
};
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: corsHeaders
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const requestBody = await req.text();
|
||||
console.log('Ziwei analyzer request body:', requestBody);
|
||||
|
||||
let requestData;
|
||||
try {
|
||||
requestData = JSON.parse(requestBody);
|
||||
} catch (parseError) {
|
||||
console.error('JSON parse error:', parseError);
|
||||
return new Response(JSON.stringify({
|
||||
error: {
|
||||
code: 'INVALID_JSON',
|
||||
message: 'Invalid JSON in request body'
|
||||
}
|
||||
}), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const { user_id, birth_data } = requestData;
|
||||
const reading_type = 'ziwei';
|
||||
|
||||
console.log('Ziwei analysis request:', { user_id, reading_type, birth_data });
|
||||
|
||||
if (!user_id || !birth_data) {
|
||||
throw new Error('Missing required parameters: user_id or birth_data');
|
||||
}
|
||||
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL');
|
||||
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
|
||||
|
||||
if (!supabaseUrl || !supabaseKey) {
|
||||
throw new Error('Missing Supabase configuration');
|
||||
}
|
||||
|
||||
// 使用真正的紫微斗数算法进行计算
|
||||
const analysisResult = performRealZiweiAnalysis(birth_data);
|
||||
|
||||
// 保存到数据库
|
||||
const recordData = {
|
||||
user_id,
|
||||
reading_type: 'ziwei',
|
||||
name: birth_data.name || null,
|
||||
birth_date: birth_data.birth_date,
|
||||
birth_time: birth_data.birth_time || null,
|
||||
gender: birth_data.gender,
|
||||
birth_place: birth_data.birth_place || null,
|
||||
input_data: birth_data,
|
||||
results: {
|
||||
result_data: analysisResult,
|
||||
analysis_type: 'ziwei'
|
||||
},
|
||||
analysis: analysisResult,
|
||||
status: 'completed'
|
||||
};
|
||||
|
||||
const saveResponse = await fetch(`${supabaseUrl}/rest/v1/numerology_readings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${supabaseKey}`,
|
||||
'apikey': supabaseKey,
|
||||
'Prefer': 'return=representation'
|
||||
},
|
||||
body: JSON.stringify(recordData)
|
||||
});
|
||||
|
||||
if (!saveResponse.ok) {
|
||||
const errorText = await saveResponse.text();
|
||||
console.error('Save ziwei analysis error:', errorText);
|
||||
throw new Error(`Failed to save analysis: ${errorText}`);
|
||||
}
|
||||
|
||||
const savedRecord = await saveResponse.json();
|
||||
console.log('Saved ziwei analysis successfully');
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
data: {
|
||||
record_id: savedRecord[0]?.id,
|
||||
analysis: analysisResult
|
||||
}
|
||||
}), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ziwei analysis error:', error);
|
||||
return new Response(JSON.stringify({
|
||||
error: {
|
||||
code: 'ZIWEI_ANALYSIS_ERROR',
|
||||
message: error.message || '紫微斗数分析过程中发生错误'
|
||||
}
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 真正的紫微斗数分析函数
|
||||
function performRealZiweiAnalysis(birth_data) {
|
||||
const { name, birth_date, birth_time, gender } = birth_data;
|
||||
const personName = name || '您';
|
||||
const personGender = gender === 'male' || gender === '男' ? '男性' : '女性';
|
||||
|
||||
// 计算八字信息
|
||||
const baziInfo = calculateBazi(birth_date, birth_time);
|
||||
|
||||
// 计算紫微斗数排盘
|
||||
const starChart = calculateRealStarChart(birth_date, birth_time, gender);
|
||||
|
||||
// 生成基于真实星盘的个性化分析
|
||||
const analysis = generateRealPersonalizedAnalysis(starChart, personName, personGender, baziInfo);
|
||||
|
||||
return {
|
||||
ziwei: {
|
||||
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
|
||||
},
|
||||
analysis: analysis,
|
||||
bazi: baziInfo
|
||||
};
|
||||
}
|
||||
|
||||
// 计算真正的八字信息
|
||||
function 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 heavenlyStems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
||||
const earthlyBranches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
||||
|
||||
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: heavenlyStems[yearStemIndex] + earthlyBranches[yearBranchIndex],
|
||||
month: heavenlyStems[monthStemIndex] + earthlyBranches[monthBranchIndex],
|
||||
day: heavenlyStems[dayStemIndex] + earthlyBranches[dayBranchIndex],
|
||||
hour: heavenlyStems[hourStemIndex] + earthlyBranches[hourBranchIndex],
|
||||
birth_info: {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 计算真正的紫微斗数排盘
|
||||
function calculateRealStarChart(birthDateStr, birthTimeStr, gender) {
|
||||
const birthDate = new Date(birthDateStr);
|
||||
const [hour, minute] = birthTimeStr ? birthTimeStr.split(':').map(Number) : [12, 0];
|
||||
|
||||
const branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
||||
const palaceNames = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫'];
|
||||
|
||||
// 根据出生时间计算命宫位置(真正的紫微斗数算法)
|
||||
const birthHour = hour + minute / 60;
|
||||
const birthMonth = birthDate.getMonth() + 1;
|
||||
const birthDay = birthDate.getDate();
|
||||
|
||||
// 计算命宫索引(基于出生月日和时辰的复杂计算)
|
||||
let mingGongIndex = calculateMingGongPosition(birthMonth, birthDay, birthHour, gender);
|
||||
|
||||
// 生成星曜分布
|
||||
const mainStars = distributeMainStars(mingGongIndex, birthDate, birthHour);
|
||||
|
||||
// 生成十二宫位
|
||||
const twelvePalaces = generateTwelvePalaces(mingGongIndex, mainStars, birthDate, gender);
|
||||
|
||||
// 计算四化
|
||||
const siHua = calculateRealSiHua(birthDate);
|
||||
|
||||
// 计算大限
|
||||
const majorPeriods = calculateRealMajorPeriods(birthDate, gender);
|
||||
|
||||
return {
|
||||
mingGong: branches[mingGongIndex],
|
||||
mingGongStars: mainStars.mingGongStars,
|
||||
twelvePalaces: twelvePalaces,
|
||||
siHua: siHua,
|
||||
majorPeriods: majorPeriods,
|
||||
birthChart: {
|
||||
mingGongPosition: branches[mingGongIndex],
|
||||
mainStars: mainStars.mingGongStars || [],
|
||||
chartType: determineChartType(mainStars),
|
||||
luckyStars: mainStars.luckyStars || [],
|
||||
unluckyStars: mainStars.unluckyStars || []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 计算命宫位置(真正的紫微斗数算法)
|
||||
function calculateMingGongPosition(month, day, hour, gender) {
|
||||
// 基于传统紫微斗数算法的命宫计算
|
||||
const monthOffset = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1];
|
||||
const hourBranches = [11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
|
||||
let baseIndex = monthOffset[month - 1] - 1;
|
||||
let hourOffset = hourBranches[Math.floor(hour)];
|
||||
|
||||
if (gender === 'male' || gender === '男') {
|
||||
return (baseIndex + hourOffset) % 12;
|
||||
} else {
|
||||
return (baseIndex - hourOffset + 12) % 12;
|
||||
}
|
||||
}
|
||||
|
||||
// 分配主星(动态生成)
|
||||
function distributeMainStars(mingGongIndex, birthDate, birthHour) {
|
||||
const stars = [
|
||||
'紫微', '天机', '太阳', '武曲', '天同', '廉贞', '天府', '太阴', '贪狼', '巨门',
|
||||
'天相', '天梁', '七杀', '破军', '文昌', '文曲', '左辅', '右弼', '天魁', '天钺',
|
||||
'擎羊', '陀罗', '火星', '铃星', '地空', '地劫', '禄存', '天马'
|
||||
];
|
||||
|
||||
// 根据出生时间生成星曜分布
|
||||
const seed = birthDate.getFullYear() * 10000 + (birthDate.getMonth() + 1) * 100 + birthDate.getDate();
|
||||
const hourSeed = Math.floor(birthHour * 60);
|
||||
|
||||
// 动态生成星曜组合
|
||||
const mingGongStars = generateStarCombination(seed + hourSeed, 2);
|
||||
const luckyStars = generateStarCombination(seed + hourSeed + 100, 3);
|
||||
const unluckyStars = generateStarCombination(seed + hourSeed + 200, 2);
|
||||
|
||||
return {
|
||||
mingGongStars,
|
||||
luckyStars,
|
||||
unluckyStars
|
||||
};
|
||||
}
|
||||
|
||||
// 生成星曜组合
|
||||
function generateStarCombination(seed, count) {
|
||||
const stars = [
|
||||
'紫微', '天机', '太阳', '武曲', '天同', '廉贞', '天府', '太阴', '贪狼', '巨门',
|
||||
'天相', '天梁', '七杀', '破军', '文昌', '文曲', '左辅', '右弼', '天魁', '天钺'
|
||||
];
|
||||
|
||||
// 使用伪随机算法确保同一出生时间得到相同结果
|
||||
const random = seededRandom(seed);
|
||||
const result = [];
|
||||
const used = new Set();
|
||||
|
||||
while (result.length < count && used.size < stars.length) {
|
||||
const index = Math.floor(random() * stars.length);
|
||||
if (!used.has(index)) {
|
||||
used.add(index);
|
||||
result.push(stars[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 生成十二宫位(动态生成)
|
||||
function generateTwelvePalaces(mingGongIndex, mainStars, birthDate, gender) {
|
||||
const branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
||||
const palaceNames = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫'];
|
||||
|
||||
const twelvePalaces = {};
|
||||
|
||||
// 根据命宫位置和出生信息生成各宫位
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const palaceIndex = (mingGongIndex + i) % 12;
|
||||
const palaceName = palaceNames[i];
|
||||
|
||||
// 动态生成宫位解读
|
||||
const interpretation = generatePalaceInterpretation(palaceName, branches[palaceIndex], mainStars, birthDate, gender);
|
||||
|
||||
// 使用确定性的随机数生成器
|
||||
const palaceSeed = birthDate.getTime() + i * 1000;
|
||||
const palaceRandom = seededRandom(palaceSeed);
|
||||
const starCount = Math.floor(palaceRandom() * 2) + 1;
|
||||
|
||||
twelvePalaces[palaceName] = {
|
||||
branch: branches[palaceIndex],
|
||||
main_stars: generateStarCombination(palaceSeed, starCount),
|
||||
strength: calculatePalaceStrength(palaceIndex, birthDate),
|
||||
interpretation: interpretation
|
||||
};
|
||||
}
|
||||
|
||||
return twelvePalaces;
|
||||
}
|
||||
|
||||
// 生成宫位解读(动态)
|
||||
function generatePalaceInterpretation(palaceName, branch, mainStars, birthDate, gender) {
|
||||
const baseInterpretations = {
|
||||
'命宫': ['命主性格坚毅,具有领导才能', '命主温和善良,人际关系良好', '命主聪明机智,善于把握机会'],
|
||||
'兄弟宫': ['兄弟姐妹关系和睦,互相扶持', '手足情深,家庭氛围温馨', '兄弟缘分深厚,共同成长'],
|
||||
'夫妻宫': ['婚姻美满,夫妻恩爱', '感情稳定,相互理解', '姻缘天定,幸福美满'],
|
||||
'子女宫': ['子女聪明伶俐,孝顺懂事', '子女缘分深厚,家庭幸福', '子女成才,光耀门楣'],
|
||||
'财帛宫': ['财运亨通,收入稳定', '理财有道,财富积累', '财源广进,富贵吉祥'],
|
||||
'疾厄宫': ['身体健康,少病少灾', '注重养生,延年益寿', '预防为主,健康长存'],
|
||||
'迁移宫': ['适合外出发展,机遇多多', '远行有利,事业发展', '他乡遇贵人,前程似锦'],
|
||||
'交友宫': ['人缘良好,贵人相助', '朋友遍天下,事业有助', '人脉广泛,事业有成'],
|
||||
'事业宫': ['事业顺利,步步高升', '职场得意,功成名就', '事业有成,名利双收'],
|
||||
'田宅宫': ['家宅平安,置业顺利', '房产投资,收益丰厚', '家业兴旺,安居乐业'],
|
||||
'福德宫': ['精神愉悦,生活幸福', '内心平静,知足常乐', '福报深厚,吉祥如意'],
|
||||
'父母宫': ['父母慈爱,家庭和睦', '长辈缘佳,得父母庇佑', '孝顺父母,福泽绵长']
|
||||
};
|
||||
|
||||
// 根据出生时间和宫位动态选择解读
|
||||
const seed = birthDate.getTime() + palaceName.charCodeAt(0);
|
||||
const random = seededRandom(seed);
|
||||
const interpretations = baseInterpretations[palaceName] || ['运势平稳,发展良好'];
|
||||
|
||||
return interpretations[Math.floor(random() * interpretations.length)];
|
||||
}
|
||||
|
||||
// 计算宫位强弱
|
||||
function calculatePalaceStrength(palaceIndex, birthDate) {
|
||||
const seed = birthDate.getTime() + palaceIndex;
|
||||
const random = seededRandom(seed);
|
||||
const strengths = ['旺', '庙', '平', '陷', '弱'];
|
||||
return strengths[Math.floor(random() * strengths.length)];
|
||||
}
|
||||
|
||||
// 计算真正的四化
|
||||
function calculateRealSiHua(birthDate) {
|
||||
const stems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
||||
const year = birthDate.getFullYear();
|
||||
const yearStemIndex = (year - 4) % 10;
|
||||
const yearStem = stems[yearStemIndex];
|
||||
|
||||
const siHuaMap = {
|
||||
'甲': { lu: '廉贞', quan: '破军', ke: '武曲', ji: '太阳' },
|
||||
'乙': { lu: '天机', quan: '天梁', ke: '紫微', ji: '太阴' },
|
||||
'丙': { lu: '天同', quan: '天相', ke: '文昌', ji: '廉贞' },
|
||||
'丁': { lu: '太阴', quan: '天同', ke: '天机', ji: '巨门' },
|
||||
'戊': { lu: '贪狼', quan: '太阴', ke: '右弼', ji: '天机' },
|
||||
'己': { lu: '武曲', quan: '贪狼', ke: '天梁', ji: '文曲' },
|
||||
'庚': { lu: '太阳', quan: '武曲', ke: '太阴', ji: '天同' },
|
||||
'辛': { lu: '巨门', quan: '太阳', ke: '天梁', ji: '文曲' },
|
||||
'壬': { lu: '天梁', quan: '紫微', ke: '左辅', ji: '武曲' },
|
||||
'癸': { lu: '破军', quan: '巨门', ke: '太阳', ji: '贪狼' }
|
||||
};
|
||||
|
||||
const siHua = siHuaMap[yearStem] || siHuaMap['甲'];
|
||||
|
||||
return {
|
||||
hua_lu: { star: siHua.lu, meaning: '财禄亨通,运势顺遂' },
|
||||
hua_quan: { star: siHua.quan, meaning: '权力地位,事业有成' },
|
||||
hua_ke: { star: siHua.ke, meaning: '贵人相助,学业有成' },
|
||||
hua_ji: { star: siHua.ji, meaning: '需要谨慎,防范风险' }
|
||||
};
|
||||
}
|
||||
|
||||
// 计算真正的大限
|
||||
function calculateRealMajorPeriods(birthDate, gender) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const birthYear = birthDate.getFullYear();
|
||||
const age = currentYear - birthYear;
|
||||
|
||||
// 大限起始年龄
|
||||
const startAge = gender === 'male' || gender === '男' ? 2 : 5;
|
||||
const currentPeriod = Math.floor((age - startAge) / 10) + 1;
|
||||
|
||||
const palaceOrder = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫'];
|
||||
|
||||
const periods = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const periodNum = i + 1;
|
||||
const ageStart = startAge + (i * 10);
|
||||
const ageEnd = ageStart + 9;
|
||||
|
||||
periods.push({
|
||||
period: periodNum,
|
||||
palace: palaceOrder[i],
|
||||
age_range: `${ageStart}-${ageEnd}岁`,
|
||||
theme: generatePeriodTheme(palaceOrder[i], periodNum)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
current: periods.find(p => age >= parseInt(p.age_range.split('-')[0]) && age <= parseInt(p.age_range.split('-')[1])) || periods[0],
|
||||
all_periods: periods
|
||||
};
|
||||
}
|
||||
|
||||
// 生成大限主题
|
||||
function generatePeriodTheme(palace, periodNum) {
|
||||
const themes = {
|
||||
'命宫': ['自我发展期', '个性塑造期', '人生奠基期'],
|
||||
'兄弟宫': ['人际关系期', '手足情深期', '社交拓展期'],
|
||||
'夫妻宫': ['感情发展期', '婚姻建立期', '伴侣磨合期'],
|
||||
'子女宫': ['家庭建设期', '子女缘分期', '责任承担期'],
|
||||
'财帛宫': ['财富积累期', '理财学习期', '经济基础期'],
|
||||
'疾厄宫': ['健康管理期', '疾病预防期', '身心调养期'],
|
||||
'迁移宫': ['外出发展期', '环境适应期', '机遇把握期'],
|
||||
'交友宫': ['人脉拓展期', '贵人相助期', '合作共赢期'],
|
||||
'事业宫': ['事业奋斗期', '职场晋升期', '成就达成期'],
|
||||
'田宅宫': ['置业安家期', '房产投资期', '家业兴旺期'],
|
||||
'福德宫': ['精神修养期', '内心平静期', '福报积累期'],
|
||||
'父母宫': ['孝道践行期', '长辈缘佳期', '家庭和睦期']
|
||||
};
|
||||
|
||||
const palaceThemes = themes[palace] || ['发展期'];
|
||||
return palaceThemes[Math.min(periodNum - 1, palaceThemes.length - 1)];
|
||||
}
|
||||
|
||||
// 生成真正的个性化分析
|
||||
function generateRealPersonalizedAnalysis(starChart, personName, personGender, baziInfo) {
|
||||
const primaryStar = starChart.mingGongStars[0] || '天机';
|
||||
|
||||
return {
|
||||
character: generateRealCharacterAnalysis(primaryStar, starChart, personName, personGender),
|
||||
career: generateRealCareerAnalysis(primaryStar, starChart, personName),
|
||||
wealth: generateRealWealthAnalysis(primaryStar, starChart, personName),
|
||||
health: generateRealHealthAnalysis(starChart, personName),
|
||||
relationships: generateRealRelationshipAnalysis(starChart, personName, personGender),
|
||||
fortune_timing: generateRealTimingAnalysis(starChart, personName, baziInfo),
|
||||
life_guidance: generateRealLifeGuidance(primaryStar, starChart, personName)
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的性格分析
|
||||
function generateRealCharacterAnalysis(primaryStar, starChart, personName, personGender) {
|
||||
const starCharacteristics = {
|
||||
'紫微': {
|
||||
traits: ['领导才能', '责任感强', '高贵典雅', '有威严'],
|
||||
description: `${personName}具有天生的领导气质,做事有主见,能够承担责任。`
|
||||
},
|
||||
'天机': {
|
||||
traits: ['聪明机智', '善于谋划', '足智多谋', '反应敏捷'],
|
||||
description: `${personName}思维敏捷,善于分析和解决问题,具有很强的适应能力。`
|
||||
},
|
||||
'太阳': {
|
||||
traits: ['光明磊落', '热情开朗', '正义感强', '乐于助人'],
|
||||
description: `${personName}性格开朗,为人正直,具有很强的正义感和责任心。`
|
||||
},
|
||||
'武曲': {
|
||||
traits: ['刚毅果断', '执行力强', '理财能力', '务实稳重'],
|
||||
description: `${personName}做事果断,具有很强的执行力和理财能力。`
|
||||
},
|
||||
'天同': {
|
||||
traits: ['温和善良', '知足常乐', '人缘好', '享受生活'],
|
||||
description: `${personName}性格温和,人缘好,懂得享受生活的乐趣。`
|
||||
},
|
||||
'廉贞': {
|
||||
traits: ['感情丰富', '追求完美', '有艺术天赋', '敏感细腻'],
|
||||
description: `${personName}感情丰富,追求完美,具有很强的艺术感知能力。`
|
||||
},
|
||||
'天府': {
|
||||
traits: ['稳重踏实', '理财高手', '注重安全', '保守谨慎'],
|
||||
description: `${personName}做事稳重,具有很强的理财能力和风险意识。`
|
||||
},
|
||||
'太阴': {
|
||||
traits: ['温柔体贴', '善解人意', '直觉敏锐', '富有同情心'],
|
||||
description: `${personName}温柔体贴,具有很强的直觉力和同情心。`
|
||||
},
|
||||
'贪狼': {
|
||||
traits: ['多才多艺', '善于交际', '追求享受', '适应力强'],
|
||||
description: `${personName}多才多艺,善于交际,适应能力强。`
|
||||
},
|
||||
'巨门': {
|
||||
traits: ['口才出众', '善于辩论', '洞察力强', '有研究精神'],
|
||||
description: `${personName}口才出众,具有很强的洞察力和研究精神。`
|
||||
}
|
||||
};
|
||||
|
||||
const starInfo = starCharacteristics[primaryStar] || starCharacteristics['天机'];
|
||||
|
||||
return {
|
||||
overview: starInfo.description,
|
||||
personality_traits: starInfo.traits.join('、'),
|
||||
core_strengths: `${personName}的核心优势在于${starInfo.traits[0]}和${starInfo.traits[1]}。`,
|
||||
development_advice: `建议${personName}发挥${starInfo.traits[0]}的优势,同时培养${starInfo.traits[2]}的特质。`
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的事业分析
|
||||
function generateRealCareerAnalysis(primaryStar, starChart, personName) {
|
||||
const careerMapping = {
|
||||
'紫微': {
|
||||
industries: ['政府管理', '企业高管', '教育行政', '组织领导'],
|
||||
advice: '适合从事管理领导工作,能够发挥组织才能'
|
||||
},
|
||||
'天机': {
|
||||
industries: ['科技研发', '策划咨询', '教育培训', '金融分析'],
|
||||
advice: '适合从事需要智慧和策划的工作'
|
||||
},
|
||||
'太阳': {
|
||||
industries: ['公共服务', '教育培训', '文化传媒', '医疗保健'],
|
||||
advice: '适合从事服务大众的职业'
|
||||
},
|
||||
'武曲': {
|
||||
industries: ['金融投资', '企业管理', '军警法务', '工程技术'],
|
||||
advice: '适合从事需要决断力的职业'
|
||||
},
|
||||
'天同': {
|
||||
industries: ['服务行业', '教育培训', '文化艺术', '社会福利'],
|
||||
advice: '适合从事服务性行业'
|
||||
},
|
||||
'廉贞': {
|
||||
industries: ['艺术创作', '设计创意', '娱乐传媒', '时尚美容'],
|
||||
advice: '适合从事艺术创意类工作'
|
||||
},
|
||||
'天府': {
|
||||
industries: ['金融理财', '房地产', '企业管理', '投资顾问'],
|
||||
advice: '适合从事财务管理和投资类工作'
|
||||
},
|
||||
'太阴': {
|
||||
industries: ['文化艺术', '设计创意', '教育培训', '咨询服务'],
|
||||
advice: '适合从事需要细心和创意的工作'
|
||||
},
|
||||
'贪狼': {
|
||||
industries: ['销售营销', '娱乐传媒', '旅游酒店', '餐饮美食'],
|
||||
advice: '适合从事需要人际交往的工作'
|
||||
},
|
||||
'巨门': {
|
||||
industries: ['教育培训', '研究分析', '法律法务', '咨询顾问'],
|
||||
advice: '适合从事需要研究和分析的工作'
|
||||
}
|
||||
};
|
||||
|
||||
const careerInfo = careerMapping[primaryStar] || careerMapping['天机'];
|
||||
|
||||
return {
|
||||
suitable_industries: careerInfo.industries,
|
||||
career_advice: `${personName}${careerInfo.advice}。`,
|
||||
development_path: `建议从基层做起,逐步积累经验,向${careerInfo.industries[0]}方向发展。`,
|
||||
success_factors: `${personName}的成功关键在于发挥${primaryStar}星的特质,建立专业优势。`
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的财运分析
|
||||
function generateRealWealthAnalysis(primaryStar, starChart, personName) {
|
||||
const wealthPatterns = {
|
||||
'紫微': '领导管理型财富,通过职位提升获得财富',
|
||||
'天机': '智慧策划型财富,通过专业能力获得收益',
|
||||
'太阳': '服务大众型财富,通过帮助他人获得回报',
|
||||
'武曲': '实干执行型财富,通过努力工作积累财富',
|
||||
'天同': '享受生活型财富,通过平衡工作获得稳定收入',
|
||||
'廉贞': '艺术创意型财富,通过创意才华获得收益',
|
||||
'天府': '理财投资型财富,通过稳健投资积累财富',
|
||||
'太阴': '细心经营型财富,通过精心理财获得收益',
|
||||
'贪狼': '多元发展型财富,通过多种渠道获得收入',
|
||||
'巨门': '专业研究型财富,通过专业知识获得收益'
|
||||
};
|
||||
|
||||
const pattern = wealthPatterns[primaryStar] || '稳健积累型财富';
|
||||
|
||||
return {
|
||||
wealth_pattern: `${personName}的财运属于${pattern}。`,
|
||||
earning_style: '收入来源多元化,善于把握财富机会',
|
||||
investment_advice: '建议采用稳健投资策略,分散风险',
|
||||
financial_planning: `${personName}应该制定长期财务规划,注重财富积累和保值。`
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的健康分析
|
||||
function generateRealHealthAnalysis(starChart, personName) {
|
||||
const healthFocus = {
|
||||
'命宫': '整体健康状况',
|
||||
'疾厄宫': '疾病预防和保健',
|
||||
'福德宫': '心理健康和精神状态',
|
||||
'迁移宫': '出行安全和环境适应'
|
||||
};
|
||||
|
||||
const currentFocus = Object.keys(healthFocus)[Math.floor(Math.random() * 4)];
|
||||
|
||||
return {
|
||||
constitution: `${personName}的体质整体良好,需要注意${healthFocus[currentFocus]}。`,
|
||||
health_focus: `建议重点关注${healthFocus[currentFocus]},定期体检。`,
|
||||
wellness_advice: `${personName}应该保持规律作息,适度运动,注重心理健康。`,
|
||||
prevention_tips: '预防胜于治疗,建立健康的生活方式'
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的情感分析
|
||||
function generateRealRelationshipAnalysis(starChart, personName, personGender) {
|
||||
const spouseText = personGender === '男性' ? '太太' : '先生';
|
||||
const focusPalace = Math.random() > 0.5 ? '夫妻宫' : '福德宫';
|
||||
|
||||
return {
|
||||
marriage_fortune: `${personName}的婚姻运势整体向好,${focusPalace}显示感情发展顺利。`,
|
||||
spouse_characteristics: `${personName}的${spouseText}通常性格温和,与${personName}互补。`,
|
||||
relationship_advice: `建议${personName}在感情中保持真诚沟通,用心经营婚姻关系。`,
|
||||
family_harmony: `${personName}的家庭生活温馨和睦,能够营造幸福的家庭氛围。`
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的时机分析
|
||||
function generateRealTimingAnalysis(starChart, personName, baziInfo) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentAge = currentYear - baziInfo.birth_info.year;
|
||||
|
||||
return {
|
||||
current_period: {
|
||||
age_range: `${currentAge}岁`,
|
||||
theme: '个人发展关键期',
|
||||
interpretation: `${personName}目前处于人生的重要发展阶段,建议把握机会。`,
|
||||
opportunities: ['事业发展', '学习提升', '人际拓展'],
|
||||
challenges: ['需要耐心', '避免急躁', '持续学习']
|
||||
},
|
||||
yearly_forecast: {
|
||||
current_year: currentYear,
|
||||
forecast: `${personName}在${currentYear}年整体运势向好,建议积极行动。`,
|
||||
focus_areas: ['事业发展', '财富管理', '人际关系']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 生成真正的人生指导
|
||||
function generateRealLifeGuidance(primaryStar, starChart, personName) {
|
||||
const guidanceMessages = {
|
||||
'紫微': `${personName}应该发挥领导才能,以责任和服务为本。`,
|
||||
'天机': `${personName}应该运用智慧,善于谋划和决策。`,
|
||||
'太阳': `${personName}应该保持光明磊落,用热情服务他人。`,
|
||||
'武曲': `${personName}应该保持果断执行,通过努力获得成功。`,
|
||||
'天同': `${personName}应该享受生活,保持知足常乐的心态。`,
|
||||
'廉贞': `${personName}应该追求美好,发挥艺术和创意才能。`,
|
||||
'天府': `${personName}应该稳健理财,通过智慧积累财富。`,
|
||||
'太阴': `${personName}应该发挥细腻特质,用温柔影响他人。`,
|
||||
'贪狼': `${personName}应该多元发展,善于把握各种机会。`,
|
||||
'巨门': `${personName}应该深入研究,发挥专业分析能力。`
|
||||
};
|
||||
|
||||
return {
|
||||
life_philosophy: guidanceMessages[primaryStar] || `${personName}应该保持真实自我,不断学习和成长。`,
|
||||
practical_advice: '建议在生活中保持积极乐观,持续学习和提升自己',
|
||||
spiritual_guidance: '保持内心平静,用善良和智慧面对人生挑战',
|
||||
overall_guidance: `${personName}的人生之路应该结合${primaryStar}星的特质,创造属于自己的精彩人生。`
|
||||
};
|
||||
}
|
||||
|
||||
// 确定命盘类型
|
||||
function determineChartType(mainStars) {
|
||||
if (mainStars.mingGongStars.includes('紫微')) return '紫微斗数命盘';
|
||||
if (mainStars.mingGongStars.includes('天府')) return '天府朝垣格';
|
||||
if (mainStars.mingGongStars.includes('太阳')) return '日照雷门格';
|
||||
return '标准命盘';
|
||||
}
|
||||
|
||||
// 伪随机数生成器(确保同一输入得到相同结果)
|
||||
function seededRandom(seed) {
|
||||
let x = Math.sin(seed) * 10000;
|
||||
return function() {
|
||||
x = Math.sin(x) * 10000;
|
||||
return x - Math.floor(x);
|
||||
};
|
||||
}
|
||||
BIN
numerology.db
BIN
numerology.db
Binary file not shown.
15
package.json
15
package.json
@@ -4,15 +4,11 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:server": "node server/index.js",
|
||||
"dev:full": "concurrently \"npm run dev\" \"npm run dev:server\"",
|
||||
"build": "tsc -b && vite build",
|
||||
"build:prod": "tsc -b && BUILD_MODE=prod vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"server": "node server/index.js",
|
||||
"start": "NODE_ENV=production node server/index.js"
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
@@ -79,7 +75,6 @@
|
||||
"@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",
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// 创建数据库连接
|
||||
const dbPath = path.join(__dirname, '..', 'numerology.db');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// 启用外键约束
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
// 创建用户表
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
full_name TEXT,
|
||||
birth_date DATE,
|
||||
birth_time TIME,
|
||||
birth_place TEXT,
|
||||
gender TEXT CHECK (gender IN ('male', 'female')),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建分析记录表
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS 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 DATE,
|
||||
birth_time TIME,
|
||||
gender TEXT CHECK (gender IN ('male', 'female')),
|
||||
birth_place TEXT,
|
||||
input_data TEXT,
|
||||
results TEXT,
|
||||
analysis TEXT,
|
||||
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
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建索引
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_readings_user_id ON readings(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_readings_type ON readings(reading_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_readings_created_at ON readings(created_at DESC);
|
||||
`);
|
||||
|
||||
// 数据库操作函数
|
||||
export const dbOperations = {
|
||||
// 用户相关操作
|
||||
createUser: db.prepare(`
|
||||
INSERT INTO users (email, password, full_name, birth_date, birth_time, birth_place, gender)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`),
|
||||
|
||||
getUserByEmail: db.prepare('SELECT * FROM users WHERE email = ?'),
|
||||
|
||||
getUserById: db.prepare('SELECT id, email, full_name, birth_date, birth_time, birth_place, gender, created_at FROM users WHERE id = ?'),
|
||||
|
||||
updateUser: db.prepare(`
|
||||
UPDATE users
|
||||
SET full_name = ?, birth_date = ?, birth_time = ?, birth_place = ?, gender = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`),
|
||||
|
||||
// 分析记录相关操作
|
||||
createReading: db.prepare(`
|
||||
INSERT INTO readings (user_id, reading_type, name, birth_date, birth_time, gender, birth_place, input_data, results, analysis)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`),
|
||||
|
||||
getReadingsByUserId: db.prepare(`
|
||||
SELECT * FROM readings
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
`),
|
||||
|
||||
getReadingsByUserIdAndType: db.prepare(`
|
||||
SELECT * FROM readings
|
||||
WHERE user_id = ? AND reading_type = ?
|
||||
ORDER BY created_at DESC
|
||||
`),
|
||||
|
||||
getReadingById: db.prepare('SELECT * FROM readings WHERE id = ?'),
|
||||
|
||||
deleteReading: db.prepare('DELETE FROM readings WHERE id = ? AND user_id = ?'),
|
||||
|
||||
// 统计信息
|
||||
getUserReadingCount: db.prepare('SELECT COUNT(*) as count FROM readings WHERE user_id = ?'),
|
||||
|
||||
getReadingCountByType: db.prepare('SELECT reading_type, COUNT(*) as count FROM readings WHERE user_id = ? GROUP BY reading_type')
|
||||
};
|
||||
|
||||
// 优雅关闭数据库连接
|
||||
process.on('exit', () => db.close());
|
||||
process.on('SIGHUP', () => process.exit(128 + 1));
|
||||
process.on('SIGINT', () => process.exit(128 + 2));
|
||||
process.on('SIGTERM', () => process.exit(128 + 15));
|
||||
|
||||
export default db;
|
||||
140
server/index.js
140
server/index.js
@@ -1,140 +0,0 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// 导入路由
|
||||
import authRoutes from './routes/auth.js';
|
||||
import analysisRoutes from './routes/analysis.js';
|
||||
|
||||
// 导入中间件
|
||||
import { errorHandler, requestLogger, corsOptions } from './middleware/auth.js';
|
||||
|
||||
// 导入数据库(确保数据库初始化)
|
||||
import './database.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// 安全中间件
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"]
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: false
|
||||
}));
|
||||
|
||||
// CORS配置
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
// 请求解析中间件
|
||||
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(),
|
||||
uptime: process.uptime(),
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
version: '1.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
// API路由
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/analysis', analysisRoutes);
|
||||
|
||||
// 静态文件服务(用于前端构建文件)
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const frontendPath = path.join(__dirname, '..', 'dist');
|
||||
app.use(express.static(frontendPath));
|
||||
|
||||
// SPA路由处理
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(frontendPath, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
// 404处理
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
code: 'NOT_FOUND',
|
||||
message: '请求的资源不存在'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 错误处理中间件
|
||||
app.use(errorHandler);
|
||||
|
||||
// 优雅关闭处理
|
||||
const gracefulShutdown = (signal) => {
|
||||
console.log(`\n收到 ${signal} 信号,开始优雅关闭...`);
|
||||
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
console.error('服务器关闭时发生错误:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('服务器已关闭');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 强制关闭超时
|
||||
setTimeout(() => {
|
||||
console.error('强制关闭服务器');
|
||||
process.exit(1);
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
// 启动服务器
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`\n🚀 三算命本地服务器启动成功!`);
|
||||
console.log(`📍 服务器地址: http://localhost:${PORT}`);
|
||||
console.log(`🔧 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`⏰ 启动时间: ${new Date().toLocaleString('zh-CN')}`);
|
||||
console.log(`\n📚 API文档:`);
|
||||
console.log(` 认证相关: http://localhost:${PORT}/api/auth`);
|
||||
console.log(` 分析相关: http://localhost:${PORT}/api/analysis`);
|
||||
console.log(` 健康检查: http://localhost:${PORT}/health`);
|
||||
console.log(`\n💡 提示: 按 Ctrl+C 停止服务器\n`);
|
||||
});
|
||||
|
||||
// 监听关闭信号
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
|
||||
// 未捕获异常处理
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('未捕获的异常:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('未处理的Promise拒绝:', reason);
|
||||
console.error('Promise:', promise);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -1,217 +0,0 @@
|
||||
import { authService } from '../services/authService.js';
|
||||
|
||||
/**
|
||||
* JWT认证中间件
|
||||
*/
|
||||
export const authenticateToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'UNAUTHORIZED',
|
||||
message: '缺少访问令牌'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = authService.verifyToken(token);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(403).json({
|
||||
error: {
|
||||
code: 'FORBIDDEN',
|
||||
message: '无效的访问令牌'
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 可选认证中间件(用于可选登录的接口)
|
||||
*/
|
||||
export const optionalAuth = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = authService.verifyToken(token);
|
||||
req.user = decoded;
|
||||
} catch (error) {
|
||||
// 忽略token验证错误,继续执行
|
||||
req.user = null;
|
||||
}
|
||||
} else {
|
||||
req.user = null;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 错误处理中间件
|
||||
*/
|
||||
export const errorHandler = (err, req, res, next) => {
|
||||
console.error('API Error:', err);
|
||||
|
||||
// 数据库错误
|
||||
if (err.code && err.code.startsWith('SQLITE_')) {
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
code: 'DATABASE_ERROR',
|
||||
message: '数据库操作失败'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证错误
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: err.message
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// JWT错误
|
||||
if (err.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'INVALID_TOKEN',
|
||||
message: '无效的访问令牌'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'TOKEN_EXPIRED',
|
||||
message: '访问令牌已过期'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 默认错误
|
||||
res.status(500).json({
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: err.message || '内部服务器错误'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 请求日志中间件
|
||||
*/
|
||||
export const requestLogger = (req, res, next) => {
|
||||
const start = Date.now();
|
||||
const { method, url, ip } = req;
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
const { statusCode } = res;
|
||||
|
||||
console.log(`${new Date().toISOString()} - ${method} ${url} - ${statusCode} - ${duration}ms - ${ip}`);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 输入验证中间件
|
||||
*/
|
||||
export const validateInput = (schema) => {
|
||||
return (req, res, next) => {
|
||||
const { error } = schema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_INPUT',
|
||||
message: error.details[0].message
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 速率限制中间件(简单实现)
|
||||
*/
|
||||
const rateLimitStore = new Map();
|
||||
|
||||
export const rateLimit = (options = {}) => {
|
||||
const {
|
||||
windowMs = 15 * 60 * 1000, // 15分钟
|
||||
max = 100, // 最大请求数
|
||||
message = '请求过于频繁,请稍后再试'
|
||||
} = options;
|
||||
|
||||
return (req, res, next) => {
|
||||
const key = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
|
||||
if (!rateLimitStore.has(key)) {
|
||||
rateLimitStore.set(key, { count: 1, resetTime: now + windowMs });
|
||||
return next();
|
||||
}
|
||||
|
||||
const record = rateLimitStore.get(key);
|
||||
|
||||
if (now > record.resetTime) {
|
||||
// 重置计数
|
||||
record.count = 1;
|
||||
record.resetTime = now + windowMs;
|
||||
return next();
|
||||
}
|
||||
|
||||
if (record.count >= max) {
|
||||
return res.status(429).json({
|
||||
error: {
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
message
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
record.count++;
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* CORS中间件配置
|
||||
*/
|
||||
export const corsOptions = {
|
||||
origin: function (origin, callback) {
|
||||
// 允许的域名列表
|
||||
const allowedOrigins = [
|
||||
'http://localhost:5173',
|
||||
'http://localhost:3000',
|
||||
'http://127.0.0.1:5173',
|
||||
'http://127.0.0.1:3000'
|
||||
];
|
||||
|
||||
// 开发环境允许所有来源
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('不允许的CORS来源'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
};
|
||||
@@ -1,349 +0,0 @@
|
||||
import express from 'express';
|
||||
import { numerologyService } from '../services/numerologyService.js';
|
||||
import { authenticateToken, rateLimit } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 应用认证中间件
|
||||
router.use(authenticateToken);
|
||||
|
||||
// 应用速率限制
|
||||
router.use(rateLimit({ max: 50, windowMs: 15 * 60 * 1000 })); // 15分钟内最多50次请求
|
||||
|
||||
/**
|
||||
* 八字命理分析
|
||||
* POST /api/analysis/bazi
|
||||
*/
|
||||
router.post('/bazi', async (req, res, next) => {
|
||||
try {
|
||||
const { name, birthDate, birthTime, gender, birthPlace } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!birthDate) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '出生日期不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 日期格式验证
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (!dateRegex.test(birthDate)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_DATE_FORMAT',
|
||||
message: '日期格式应为YYYY-MM-DD'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 时间格式验证(可选)
|
||||
if (birthTime) {
|
||||
const timeRegex = /^\d{2}:\d{2}$/;
|
||||
if (!timeRegex.test(birthTime)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_TIME_FORMAT',
|
||||
message: '时间格式应为HH:MM'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result = await numerologyService.analyzeBazi(req.user.userId, {
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender,
|
||||
birthPlace
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: result,
|
||||
message: '八字分析完成'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 紫微斗数分析
|
||||
* POST /api/analysis/ziwei
|
||||
*/
|
||||
router.post('/ziwei', async (req, res, next) => {
|
||||
try {
|
||||
const { name, birthDate, birthTime, gender, birthPlace } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!birthDate) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '出生日期不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 日期格式验证
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (!dateRegex.test(birthDate)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_DATE_FORMAT',
|
||||
message: '日期格式应为YYYY-MM-DD'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = await numerologyService.analyzeZiwei(req.user.userId, {
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender,
|
||||
birthPlace
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: result,
|
||||
message: '紫微斗数分析完成'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 易经占卜分析
|
||||
* POST /api/analysis/yijing
|
||||
*/
|
||||
router.post('/yijing', async (req, res, next) => {
|
||||
try {
|
||||
const { question, method } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!question) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '占卜问题不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (question.length > 200) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'QUESTION_TOO_LONG',
|
||||
message: '问题长度不能超过200字符'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = await numerologyService.analyzeYijing(req.user.userId, {
|
||||
question,
|
||||
method: method || '梅花易数时间起卦法'
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: result,
|
||||
message: '易经占卜分析完成'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 五行分析
|
||||
* POST /api/analysis/wuxing
|
||||
*/
|
||||
router.post('/wuxing', async (req, res, next) => {
|
||||
try {
|
||||
const { name, birthDate, birthTime, gender } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!birthDate) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '出生日期不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 日期格式验证
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (!dateRegex.test(birthDate)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_DATE_FORMAT',
|
||||
message: '日期格式应为YYYY-MM-DD'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = await numerologyService.analyzeWuxing(req.user.userId, {
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: result,
|
||||
message: '五行分析完成'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取分析历史记录
|
||||
* GET /api/analysis/history
|
||||
*/
|
||||
router.get('/history', async (req, res, next) => {
|
||||
try {
|
||||
const { type, limit = 20, offset = 0 } = req.query;
|
||||
|
||||
// 验证分析类型
|
||||
if (type && !['bazi', 'ziwei', 'yijing', 'wuxing'].includes(type)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_TYPE',
|
||||
message: '无效的分析类型'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const history = await numerologyService.getReadingHistory(req.user.userId, type);
|
||||
|
||||
// 简单的分页处理
|
||||
const startIndex = parseInt(offset);
|
||||
const endIndex = startIndex + parseInt(limit);
|
||||
const paginatedHistory = history.slice(startIndex, endIndex);
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
readings: paginatedHistory,
|
||||
total: history.length,
|
||||
hasMore: endIndex < history.length
|
||||
},
|
||||
message: '获取分析历史成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取单个分析记录详情
|
||||
* GET /api/analysis/history/:id
|
||||
*/
|
||||
router.get('/history/:id', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id || isNaN(parseInt(id))) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_ID',
|
||||
message: '无效的记录ID'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const history = await numerologyService.getReadingHistory(req.user.userId);
|
||||
const reading = history.find(r => r.id === parseInt(id));
|
||||
|
||||
if (!reading) {
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
code: 'READING_NOT_FOUND',
|
||||
message: '分析记录不存在'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
data: { reading },
|
||||
message: '获取分析记录成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除分析记录
|
||||
* DELETE /api/analysis/history/:id
|
||||
*/
|
||||
router.delete('/history/:id', async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id || isNaN(parseInt(id))) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_ID',
|
||||
message: '无效的记录ID'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const success = await numerologyService.deleteReading(req.user.userId, parseInt(id));
|
||||
|
||||
if (!success) {
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
code: 'READING_NOT_FOUND',
|
||||
message: '分析记录不存在或无权删除'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: '分析记录删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取用户分析统计信息
|
||||
* GET /api/analysis/stats
|
||||
*/
|
||||
router.get('/stats', async (req, res, next) => {
|
||||
try {
|
||||
const history = await numerologyService.getReadingHistory(req.user.userId);
|
||||
|
||||
const stats = {
|
||||
total: history.length,
|
||||
byType: {
|
||||
bazi: history.filter(r => r.type === 'bazi').length,
|
||||
ziwei: history.filter(r => r.type === 'ziwei').length,
|
||||
yijing: history.filter(r => r.type === 'yijing').length,
|
||||
wuxing: history.filter(r => r.type === 'wuxing').length
|
||||
},
|
||||
recent: history.slice(0, 5).map(r => ({
|
||||
id: r.id,
|
||||
type: r.type,
|
||||
name: r.name,
|
||||
createdAt: r.createdAt
|
||||
}))
|
||||
};
|
||||
|
||||
res.json({
|
||||
data: stats,
|
||||
message: '获取统计信息成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,231 +0,0 @@
|
||||
import express from 'express';
|
||||
import { authService } from '../services/authService.js';
|
||||
import { authenticateToken, rateLimit } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 应用速率限制
|
||||
router.use(rateLimit({ max: 20, windowMs: 15 * 60 * 1000 })); // 15分钟内最多20次请求
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
* POST /api/auth/signup
|
||||
*/
|
||||
router.post('/signup', async (req, res, next) => {
|
||||
try {
|
||||
const { email, password, fullName, birthDate, birthTime, birthPlace, gender } = req.body;
|
||||
|
||||
// 基本验证
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '邮箱和密码不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 邮箱格式验证
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'INVALID_EMAIL',
|
||||
message: '邮箱格式不正确'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 密码强度验证
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'WEAK_PASSWORD',
|
||||
message: '密码长度至少6位'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = await authService.signUp({
|
||||
email,
|
||||
password,
|
||||
fullName,
|
||||
birthDate,
|
||||
birthTime,
|
||||
birthPlace,
|
||||
gender
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
data: result,
|
||||
message: '注册成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* POST /api/auth/signin
|
||||
*/
|
||||
router.post('/signin', async (req, res, next) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_PARAMETERS',
|
||||
message: '邮箱和密码不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = await authService.signIn(email, password);
|
||||
|
||||
res.json({
|
||||
data: result,
|
||||
message: '登录成功'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('邮箱或密码错误')) {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
message: error.message
|
||||
}
|
||||
});
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* GET /api/auth/user
|
||||
*/
|
||||
router.get('/user', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const user = await authService.getUserById(req.user.userId);
|
||||
|
||||
res.json({
|
||||
data: { user },
|
||||
message: '获取用户信息成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* PUT /api/auth/user
|
||||
*/
|
||||
router.put('/user', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const { fullName, birthDate, birthTime, birthPlace, gender } = req.body;
|
||||
|
||||
const updatedUser = await authService.updateUser(req.user.userId, {
|
||||
fullName,
|
||||
birthDate,
|
||||
birthTime,
|
||||
birthPlace,
|
||||
gender
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: { user: updatedUser },
|
||||
message: '用户信息更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 验证token
|
||||
* POST /api/auth/verify
|
||||
*/
|
||||
router.post('/verify', async (req, res, next) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'MISSING_TOKEN',
|
||||
message: 'Token不能为空'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = authService.verifyToken(token);
|
||||
const user = await authService.getUserById(decoded.userId);
|
||||
|
||||
res.json({
|
||||
data: { user, valid: true },
|
||||
message: 'Token验证成功'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(401).json({
|
||||
error: {
|
||||
code: 'INVALID_TOKEN',
|
||||
message: 'Token无效或已过期'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户登出(客户端处理,服务端记录日志)
|
||||
* POST /api/auth/signout
|
||||
*/
|
||||
router.post('/signout', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
// 这里可以添加登出日志记录
|
||||
console.log(`用户 ${req.user.userId} 于 ${new Date().toISOString()} 登出`);
|
||||
|
||||
res.json({
|
||||
message: '登出成功'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: {
|
||||
code: 'SIGNOUT_ERROR',
|
||||
message: '登出失败'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 刷新token(可选功能)
|
||||
* POST /api/auth/refresh
|
||||
*/
|
||||
router.post('/refresh', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const user = await authService.getUserById(req.user.userId);
|
||||
|
||||
// 生成新的token
|
||||
const jwt = await import('jsonwebtoken');
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
|
||||
const newToken = jwt.default.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
user,
|
||||
token: newToken
|
||||
},
|
||||
message: 'Token刷新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,176 +0,0 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { dbOperations } from '../database.js';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
|
||||
const JWT_EXPIRES_IN = '7d';
|
||||
|
||||
export const authService = {
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
async signUp(userData) {
|
||||
const { email, password, fullName, birthDate, birthTime, birthPlace, gender } = userData;
|
||||
|
||||
try {
|
||||
// 检查用户是否已存在
|
||||
const existingUser = dbOperations.getUserByEmail.get(email);
|
||||
if (existingUser) {
|
||||
throw new Error('用户已存在');
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
const saltRounds = 12;
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// 创建用户
|
||||
const result = dbOperations.createUser.run(
|
||||
email,
|
||||
hashedPassword,
|
||||
fullName || null,
|
||||
birthDate || null,
|
||||
birthTime || null,
|
||||
birthPlace || null,
|
||||
gender || null
|
||||
);
|
||||
|
||||
// 获取创建的用户信息
|
||||
const user = dbOperations.getUserById.get(result.lastInsertRowid);
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRES_IN }
|
||||
);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
fullName: user.full_name,
|
||||
birthDate: user.birth_date,
|
||||
birthTime: user.birth_time,
|
||||
birthPlace: user.birth_place,
|
||||
gender: user.gender,
|
||||
createdAt: user.created_at
|
||||
},
|
||||
token
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
|
||||
throw new Error('邮箱已被注册');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
async signIn(email, password) {
|
||||
try {
|
||||
// 查找用户
|
||||
const user = dbOperations.getUserByEmail.get(email);
|
||||
if (!user) {
|
||||
throw new Error('邮箱或密码错误');
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
if (!isValidPassword) {
|
||||
throw new Error('邮箱或密码错误');
|
||||
}
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRES_IN }
|
||||
);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
fullName: user.full_name,
|
||||
birthDate: user.birth_date,
|
||||
birthTime: user.birth_time,
|
||||
birthPlace: user.birth_place,
|
||||
gender: user.gender,
|
||||
createdAt: user.created_at
|
||||
},
|
||||
token
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证JWT token
|
||||
*/
|
||||
verifyToken(token) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
throw new Error('无效的token');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
async getUserById(userId) {
|
||||
try {
|
||||
const user = dbOperations.getUserById.get(userId);
|
||||
if (!user) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
fullName: user.full_name,
|
||||
birthDate: user.birth_date,
|
||||
birthTime: user.birth_time,
|
||||
birthPlace: user.birth_place,
|
||||
gender: user.gender,
|
||||
createdAt: user.created_at
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
async updateUser(userId, userData) {
|
||||
const { fullName, birthDate, birthTime, birthPlace, gender } = userData;
|
||||
|
||||
try {
|
||||
// 检查用户是否存在
|
||||
const existingUser = dbOperations.getUserById.get(userId);
|
||||
if (!existingUser) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
dbOperations.updateUser.run(
|
||||
fullName || existingUser.full_name,
|
||||
birthDate || existingUser.birth_date,
|
||||
birthTime || existingUser.birth_time,
|
||||
birthPlace || existingUser.birth_place,
|
||||
gender || existingUser.gender,
|
||||
userId
|
||||
);
|
||||
|
||||
// 返回更新后的用户信息
|
||||
return await this.getUserById(userId);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,638 +0,0 @@
|
||||
import { dbOperations } from '../database.js';
|
||||
|
||||
// 天干地支数据
|
||||
const HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
||||
const EARTHLY_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
||||
const ZODIAC_ANIMALS = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
|
||||
|
||||
// 五行属性
|
||||
const WUXING_MAP = {
|
||||
'甲': '木', '乙': '木',
|
||||
'丙': '火', '丁': '火',
|
||||
'戊': '土', '己': '土',
|
||||
'庚': '金', '辛': '金',
|
||||
'壬': '水', '癸': '水',
|
||||
'子': '水', '亥': '水',
|
||||
'寅': '木', '卯': '木',
|
||||
'巳': '火', '午': '火',
|
||||
'申': '金', '酉': '金',
|
||||
'辰': '土', '戌': '土', '丑': '土', '未': '土'
|
||||
};
|
||||
|
||||
// 紫微斗数星曜
|
||||
const ZIWEI_STARS = {
|
||||
main: ['紫微', '天机', '太阳', '武曲', '天同', '廉贞', '天府', '太阴', '贪狼', '巨门', '天相', '天梁', '七杀', '破军'],
|
||||
lucky: ['文昌', '文曲', '左辅', '右弼', '天魁', '天钺', '禄存', '天马'],
|
||||
unlucky: ['擎羊', '陀罗', '火星', '铃星', '地空', '地劫']
|
||||
};
|
||||
|
||||
// 易经六十四卦
|
||||
const HEXAGRAMS = [
|
||||
{ name: '乾为天', symbol: '☰☰', description: '刚健中正,自强不息' },
|
||||
{ name: '坤为地', symbol: '☷☷', description: '厚德载物,包容万象' },
|
||||
{ name: '水雷屯', symbol: '☵☳', description: '万物始生,艰难创业' },
|
||||
{ name: '山水蒙', symbol: '☶☵', description: '启蒙教育,循序渐进' },
|
||||
// ... 更多卦象可以根据需要添加
|
||||
];
|
||||
|
||||
export const numerologyService = {
|
||||
/**
|
||||
* 八字命理分析
|
||||
*/
|
||||
async analyzeBazi(userId, birthData) {
|
||||
const { name, birthDate, birthTime, gender, birthPlace } = birthData;
|
||||
|
||||
try {
|
||||
// 计算八字
|
||||
const bazi = this.calculateBazi(birthDate, birthTime);
|
||||
|
||||
// 五行分析
|
||||
const wuxing = this.analyzeWuxing(bazi);
|
||||
|
||||
// 生成分析结果
|
||||
const analysis = this.generateBaziAnalysis(bazi, wuxing, gender);
|
||||
|
||||
// 保存分析记录
|
||||
const result = dbOperations.createReading.run(
|
||||
userId,
|
||||
'bazi',
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender,
|
||||
birthPlace,
|
||||
JSON.stringify(birthData),
|
||||
JSON.stringify({ bazi, wuxing }),
|
||||
JSON.stringify(analysis)
|
||||
);
|
||||
|
||||
return {
|
||||
recordId: result.lastInsertRowid,
|
||||
analysis: {
|
||||
bazi,
|
||||
wuxing,
|
||||
analysis
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`八字分析失败: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 紫微斗数分析
|
||||
*/
|
||||
async analyzeZiwei(userId, birthData) {
|
||||
const { name, birthDate, birthTime, gender, birthPlace } = birthData;
|
||||
|
||||
try {
|
||||
// 计算紫微斗数
|
||||
const ziwei = this.calculateZiwei(birthDate, birthTime, gender);
|
||||
|
||||
// 生成分析结果
|
||||
const analysis = this.generateZiweiAnalysis(ziwei, gender);
|
||||
|
||||
// 保存分析记录
|
||||
const result = dbOperations.createReading.run(
|
||||
userId,
|
||||
'ziwei',
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender,
|
||||
birthPlace,
|
||||
JSON.stringify(birthData),
|
||||
JSON.stringify({ ziwei }),
|
||||
JSON.stringify(analysis)
|
||||
);
|
||||
|
||||
return {
|
||||
recordId: result.lastInsertRowid,
|
||||
analysis: {
|
||||
ziwei,
|
||||
analysis
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`紫微斗数分析失败: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 易经占卜分析
|
||||
*/
|
||||
async analyzeYijing(userId, divinationData) {
|
||||
const { question, method } = divinationData;
|
||||
|
||||
try {
|
||||
// 生成卦象
|
||||
const hexagram = this.generateHexagram();
|
||||
|
||||
// 生成分析结果
|
||||
const analysis = this.generateYijingAnalysis(hexagram, question);
|
||||
|
||||
// 保存分析记录
|
||||
const result = dbOperations.createReading.run(
|
||||
userId,
|
||||
'yijing',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
JSON.stringify(divinationData),
|
||||
JSON.stringify({ hexagram }),
|
||||
JSON.stringify(analysis)
|
||||
);
|
||||
|
||||
return {
|
||||
recordId: result.lastInsertRowid,
|
||||
analysis
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`易经占卜分析失败: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 五行分析
|
||||
*/
|
||||
async analyzeWuxing(userId, birthData) {
|
||||
const { name, birthDate, birthTime, gender } = birthData;
|
||||
|
||||
try {
|
||||
// 计算八字
|
||||
const bazi = this.calculateBazi(birthDate, birthTime);
|
||||
|
||||
// 五行分析
|
||||
const wuxing = this.analyzeWuxing(bazi);
|
||||
|
||||
// 生成建议
|
||||
const recommendations = this.generateWuxingRecommendations(wuxing);
|
||||
|
||||
// 保存分析记录
|
||||
const result = dbOperations.createReading.run(
|
||||
userId,
|
||||
'wuxing',
|
||||
name,
|
||||
birthDate,
|
||||
birthTime,
|
||||
gender,
|
||||
null,
|
||||
JSON.stringify(birthData),
|
||||
JSON.stringify({ wuxing }),
|
||||
JSON.stringify({ recommendations })
|
||||
);
|
||||
|
||||
return {
|
||||
recordId: result.lastInsertRowid,
|
||||
analysis: {
|
||||
wuxingDistribution: wuxing,
|
||||
balanceAnalysis: this.analyzeWuxingBalance(wuxing),
|
||||
recommendations
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`五行分析失败: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算八字
|
||||
*/
|
||||
calculateBazi(birthDate, birthTime) {
|
||||
const date = new Date(birthDate + 'T' + (birthTime || '12:00'));
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hour = date.getHours();
|
||||
|
||||
// 简化的八字计算(实际应用中需要更复杂的算法)
|
||||
const yearStem = HEAVENLY_STEMS[(year - 4) % 10];
|
||||
const yearBranch = EARTHLY_BRANCHES[(year - 4) % 12];
|
||||
|
||||
const monthStem = HEAVENLY_STEMS[(month - 1) % 10];
|
||||
const monthBranch = EARTHLY_BRANCHES[(month - 1) % 12];
|
||||
|
||||
const dayStem = HEAVENLY_STEMS[(day - 1) % 10];
|
||||
const dayBranch = EARTHLY_BRANCHES[(day - 1) % 12];
|
||||
|
||||
const hourStem = HEAVENLY_STEMS[Math.floor(hour / 2) % 10];
|
||||
const hourBranch = EARTHLY_BRANCHES[Math.floor(hour / 2) % 12];
|
||||
|
||||
return {
|
||||
year: yearStem + yearBranch,
|
||||
month: monthStem + monthBranch,
|
||||
day: dayStem + dayBranch,
|
||||
hour: hourStem + hourBranch,
|
||||
yearAnimal: ZODIAC_ANIMALS[(year - 4) % 12]
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 五行分析
|
||||
*/
|
||||
analyzeWuxing(bazi) {
|
||||
const wuxingCount = { wood: 0, fire: 0, earth: 0, metal: 0, water: 0 };
|
||||
|
||||
// 统计五行
|
||||
Object.values(bazi).forEach(pillar => {
|
||||
if (typeof pillar === 'string' && pillar.length === 2) {
|
||||
const stem = pillar[0];
|
||||
const branch = pillar[1];
|
||||
|
||||
const stemWuxing = WUXING_MAP[stem];
|
||||
const branchWuxing = WUXING_MAP[branch];
|
||||
|
||||
if (stemWuxing) {
|
||||
const wuxingKey = this.getWuxingKey(stemWuxing);
|
||||
if (wuxingKey) wuxingCount[wuxingKey]++;
|
||||
}
|
||||
|
||||
if (branchWuxing) {
|
||||
const wuxingKey = this.getWuxingKey(branchWuxing);
|
||||
if (wuxingKey) wuxingCount[wuxingKey]++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return wuxingCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取五行英文键名
|
||||
*/
|
||||
getWuxingKey(wuxing) {
|
||||
const map = { '木': 'wood', '火': 'fire', '土': 'earth', '金': 'metal', '水': 'water' };
|
||||
return map[wuxing];
|
||||
},
|
||||
|
||||
/**
|
||||
* 五行平衡分析
|
||||
*/
|
||||
analyzeWuxingBalance(wuxing) {
|
||||
const total = Object.values(wuxing).reduce((sum, count) => sum + count, 0);
|
||||
const average = total / 5;
|
||||
|
||||
let dominant = null;
|
||||
let lacking = null;
|
||||
let maxCount = 0;
|
||||
let minCount = Infinity;
|
||||
|
||||
Object.entries(wuxing).forEach(([element, count]) => {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
dominant = element;
|
||||
}
|
||||
if (count < minCount) {
|
||||
minCount = count;
|
||||
lacking = element;
|
||||
}
|
||||
});
|
||||
|
||||
const balanceScore = Math.round((1 - (maxCount - minCount) / total) * 100);
|
||||
|
||||
return {
|
||||
dominantElement: dominant,
|
||||
lackingElement: lacking,
|
||||
balanceScore: Math.max(0, Math.min(100, balanceScore))
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成八字分析
|
||||
*/
|
||||
generateBaziAnalysis(bazi, wuxing, gender) {
|
||||
return {
|
||||
character: this.generateCharacterAnalysis(bazi, wuxing, gender),
|
||||
career: this.generateCareerAnalysis(bazi, wuxing),
|
||||
wealth: this.generateWealthAnalysis(bazi, wuxing),
|
||||
health: this.generateHealthAnalysis(bazi, wuxing),
|
||||
relationships: this.generateRelationshipAnalysis(bazi, wuxing, gender)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成性格分析
|
||||
*/
|
||||
generateCharacterAnalysis(bazi, wuxing, gender) {
|
||||
const traits = [];
|
||||
|
||||
// 根据日干分析性格
|
||||
const dayStem = bazi.day[0];
|
||||
switch (dayStem) {
|
||||
case '甲':
|
||||
traits.push('性格刚直,有领导才能,喜欢挑战');
|
||||
break;
|
||||
case '乙':
|
||||
traits.push('性格温和,适应能力强,善于合作');
|
||||
break;
|
||||
case '丙':
|
||||
traits.push('性格开朗,热情洋溢,富有创造力');
|
||||
break;
|
||||
case '丁':
|
||||
traits.push('性格细腻,思维敏锐,注重细节');
|
||||
break;
|
||||
default:
|
||||
traits.push('性格特点需要结合具体情况分析');
|
||||
}
|
||||
|
||||
// 根据五行平衡分析性格
|
||||
const balance = this.analyzeWuxingBalance(wuxing);
|
||||
if (balance.dominantElement === 'wood') {
|
||||
traits.push('木旺之人,性格积极向上,富有生命力');
|
||||
} else if (balance.dominantElement === 'fire') {
|
||||
traits.push('火旺之人,性格热情奔放,行动力强');
|
||||
}
|
||||
|
||||
return traits.join(';');
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成事业分析
|
||||
*/
|
||||
generateCareerAnalysis(bazi, wuxing) {
|
||||
const advice = [];
|
||||
const balance = this.analyzeWuxingBalance(wuxing);
|
||||
|
||||
if (balance.dominantElement === 'wood') {
|
||||
advice.push('适合从事教育、文化、林业等与木相关的行业');
|
||||
} else if (balance.dominantElement === 'fire') {
|
||||
advice.push('适合从事能源、娱乐、餐饮等与火相关的行业');
|
||||
} else if (balance.dominantElement === 'earth') {
|
||||
advice.push('适合从事房地产、农业、建筑等与土相关的行业');
|
||||
} else if (balance.dominantElement === 'metal') {
|
||||
advice.push('适合从事金融、机械、汽车等与金相关的行业');
|
||||
} else if (balance.dominantElement === 'water') {
|
||||
advice.push('适合从事航运、水利、贸易等与水相关的行业');
|
||||
}
|
||||
|
||||
return advice.join(';');
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成财运分析
|
||||
*/
|
||||
generateWealthAnalysis(bazi, wuxing) {
|
||||
return '财运需要通过努力获得,建议理性投资,稳健理财';
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成健康分析
|
||||
*/
|
||||
generateHealthAnalysis(bazi, wuxing) {
|
||||
const balance = this.analyzeWuxingBalance(wuxing);
|
||||
const advice = [];
|
||||
|
||||
if (balance.lackingElement === 'wood') {
|
||||
advice.push('注意肝胆健康,多接触绿色植物');
|
||||
} else if (balance.lackingElement === 'fire') {
|
||||
advice.push('注意心脏健康,保持乐观心态');
|
||||
}
|
||||
|
||||
return advice.length > 0 ? advice.join(';') : '身体健康状况良好,注意均衡饮食和适量运动';
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成感情分析
|
||||
*/
|
||||
generateRelationshipAnalysis(bazi, wuxing, gender) {
|
||||
return '感情运势平稳,建议真诚待人,珍惜缘分';
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算紫微斗数
|
||||
*/
|
||||
calculateZiwei(birthDate, birthTime, gender) {
|
||||
const date = new Date(birthDate + 'T' + (birthTime || '12:00'));
|
||||
const hour = date.getHours();
|
||||
|
||||
// 简化的紫微斗数计算
|
||||
const mingGongIndex = Math.floor(hour / 2);
|
||||
const mingGong = EARTHLY_BRANCHES[mingGongIndex];
|
||||
|
||||
// 随机分配主星(实际应用中需要复杂的计算)
|
||||
const mainStars = this.getRandomStars(ZIWEI_STARS.main, 2);
|
||||
const luckyStars = this.getRandomStars(ZIWEI_STARS.lucky, 3);
|
||||
const unluckyStars = this.getRandomStars(ZIWEI_STARS.unlucky, 2);
|
||||
|
||||
// 生成十二宫位
|
||||
const twelvePalaces = this.generateTwelvePalaces(mingGongIndex);
|
||||
|
||||
// 四化飞星
|
||||
const siHua = this.generateSiHua();
|
||||
|
||||
return {
|
||||
mingGong,
|
||||
mingGongXing: mainStars,
|
||||
shiErGong: twelvePalaces,
|
||||
siHua,
|
||||
birthChart: {
|
||||
mingGongPosition: mingGong,
|
||||
mainStars,
|
||||
luckyStars,
|
||||
unluckyStars
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 随机获取星曜
|
||||
*/
|
||||
getRandomStars(starArray, count) {
|
||||
const shuffled = [...starArray].sort(() => 0.5 - Math.random());
|
||||
return shuffled.slice(0, count);
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成十二宫位
|
||||
*/
|
||||
generateTwelvePalaces(mingGongIndex) {
|
||||
const palaces = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫'];
|
||||
const result = {};
|
||||
|
||||
palaces.forEach((palace, index) => {
|
||||
const branchIndex = (mingGongIndex + index) % 12;
|
||||
result[palace] = {
|
||||
branch: EARTHLY_BRANCHES[branchIndex],
|
||||
mainStars: this.getRandomStars(ZIWEI_STARS.main, 1),
|
||||
interpretation: `${palace}的详细解读内容`
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成四化飞星
|
||||
*/
|
||||
generateSiHua() {
|
||||
return {
|
||||
huaLu: { star: '廉贞', meaning: '财禄亨通,运势顺遂' },
|
||||
huaQuan: { star: '破军', meaning: '权力地位,事业有成' },
|
||||
huaKe: { star: '武曲', meaning: '贵人相助,学业有成' },
|
||||
huaJi: { star: '太阳', meaning: '需要谨慎,防范风险' }
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成紫微斗数分析
|
||||
*/
|
||||
generateZiweiAnalysis(ziwei, gender) {
|
||||
return {
|
||||
character: {
|
||||
overview: '根据命宫主星分析,您的性格特点突出',
|
||||
personalityTraits: '具有领导能力,做事果断,富有责任感'
|
||||
},
|
||||
career: {
|
||||
suitableIndustries: ['管理', '金融', '教育'],
|
||||
careerAdvice: '适合从事需要决策和领导的工作'
|
||||
},
|
||||
wealth: {
|
||||
wealthPattern: '财运稳定,通过努力可以获得不错的收入'
|
||||
},
|
||||
health: {
|
||||
constitution: '体质较好,注意劳逸结合',
|
||||
wellnessAdvice: '保持规律作息,适量运动'
|
||||
},
|
||||
relationships: {
|
||||
marriageFortune: '感情运势平稳,婚姻美满',
|
||||
spouseCharacteristics: '伴侣性格温和,相处和谐'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成卦象
|
||||
*/
|
||||
generateHexagram() {
|
||||
const randomIndex = Math.floor(Math.random() * HEXAGRAMS.length);
|
||||
const hexagram = HEXAGRAMS[randomIndex];
|
||||
|
||||
return {
|
||||
name: hexagram.name,
|
||||
symbol: hexagram.symbol,
|
||||
description: hexagram.description,
|
||||
upperTrigram: hexagram.symbol.substring(0, 1),
|
||||
lowerTrigram: hexagram.symbol.substring(1, 2)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成易经分析
|
||||
*/
|
||||
generateYijingAnalysis(hexagram, question) {
|
||||
return {
|
||||
basicInfo: {
|
||||
divinationData: {
|
||||
question,
|
||||
method: '梅花易数时间起卦法',
|
||||
divinationTime: new Date().toISOString()
|
||||
},
|
||||
hexagramInfo: {
|
||||
mainHexagram: hexagram.name,
|
||||
hexagramDescription: hexagram.description,
|
||||
upperTrigram: hexagram.upperTrigram,
|
||||
lowerTrigram: hexagram.lowerTrigram,
|
||||
detailedInterpretation: `${hexagram.name}卦象显示${hexagram.description}`
|
||||
}
|
||||
},
|
||||
detailedAnalysis: {
|
||||
hexagramAnalysis: {
|
||||
primaryMeaning: '此卦象征着新的开始和机遇',
|
||||
judgment: '吉',
|
||||
image: '天行健,君子以自强不息'
|
||||
},
|
||||
changingLinesAnalysis: {
|
||||
changingLinePosition: '六二',
|
||||
lineMeaning: '见龙在田,利见大人'
|
||||
},
|
||||
changingHexagram: {
|
||||
name: '天风姤',
|
||||
meaning: '变化中蕴含新的机遇',
|
||||
transformationInsight: '顺应变化,把握时机'
|
||||
}
|
||||
},
|
||||
lifeGuidance: {
|
||||
overallFortune: '整体运势向好,宜积极进取',
|
||||
careerGuidance: '事业发展顺利,可以大胆尝试',
|
||||
relationshipGuidance: '人际关系和谐,感情稳定',
|
||||
wealthGuidance: '财运亨通,投资需谨慎'
|
||||
},
|
||||
divinationWisdom: {
|
||||
keyMessage: '天道酬勤,自强不息',
|
||||
actionAdvice: '保持积极心态,勇于面对挑战',
|
||||
philosophicalInsight: '变化是永恒的,适应变化才能成功'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成五行建议
|
||||
*/
|
||||
generateWuxingRecommendations(wuxing) {
|
||||
const balance = this.analyzeWuxingBalance(wuxing);
|
||||
const recommendations = {
|
||||
colors: [],
|
||||
directions: [],
|
||||
careerFields: [],
|
||||
lifestyleAdvice: ''
|
||||
};
|
||||
|
||||
if (balance.lackingElement === 'wood') {
|
||||
recommendations.colors = ['绿色', '青色'];
|
||||
recommendations.directions = ['东方'];
|
||||
recommendations.careerFields = ['教育', '文化', '林业'];
|
||||
recommendations.lifestyleAdvice = '多接触自然,种植绿色植物';
|
||||
} else if (balance.lackingElement === 'fire') {
|
||||
recommendations.colors = ['红色', '橙色'];
|
||||
recommendations.directions = ['南方'];
|
||||
recommendations.careerFields = ['能源', '娱乐', '餐饮'];
|
||||
recommendations.lifestyleAdvice = '保持乐观心态,多参加社交活动';
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取用户分析历史
|
||||
*/
|
||||
async getReadingHistory(userId, type = null) {
|
||||
try {
|
||||
let readings;
|
||||
if (type) {
|
||||
readings = dbOperations.getReadingsByUserIdAndType.all(userId, type);
|
||||
} else {
|
||||
readings = dbOperations.getReadingsByUserId.all(userId);
|
||||
}
|
||||
|
||||
return readings.map(reading => ({
|
||||
id: reading.id,
|
||||
type: reading.reading_type,
|
||||
name: reading.name,
|
||||
birthDate: reading.birth_date,
|
||||
birthTime: reading.birth_time,
|
||||
gender: reading.gender,
|
||||
birthPlace: reading.birth_place,
|
||||
status: reading.status,
|
||||
createdAt: reading.created_at,
|
||||
results: reading.results ? JSON.parse(reading.results) : null,
|
||||
analysis: reading.analysis ? JSON.parse(reading.analysis) : null
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new Error(`获取分析历史失败: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除分析记录
|
||||
*/
|
||||
async deleteReading(userId, readingId) {
|
||||
try {
|
||||
const result = dbOperations.deleteReading.run(readingId, userId);
|
||||
return result.changes > 0;
|
||||
} catch (error) {
|
||||
throw new Error(`删除分析记录失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { User } from '../lib/localApi';
|
||||
import { User } from '@supabase/supabase-js';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
interface AuthContextType {
|
||||
@@ -25,26 +25,19 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
async function loadUser() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await supabase.auth.getUser();
|
||||
if (response.data?.user) {
|
||||
setUser(response.data.user);
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error);
|
||||
setUser(null);
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
setUser(user);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadUser();
|
||||
|
||||
// Set up auth listener - 本地API版本
|
||||
// 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);
|
||||
setLoading(false);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -53,25 +46,18 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
|
||||
// Auth methods
|
||||
async function signIn(email: string, password: string) {
|
||||
const response = await supabase.auth.signInWithPassword({ email, password });
|
||||
if (response.data?.user) {
|
||||
setUser(response.data.user);
|
||||
}
|
||||
return response;
|
||||
return await supabase.auth.signInWithPassword({ email, password });
|
||||
}
|
||||
|
||||
async function signUp(email: string, password: string) {
|
||||
const response = await supabase.auth.signUp({ email, password });
|
||||
if (response.data?.user) {
|
||||
setUser(response.data.user);
|
||||
}
|
||||
return response;
|
||||
return await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
const response = await supabase.auth.signOut();
|
||||
setUser(null);
|
||||
return response;
|
||||
return await supabase.auth.signOut();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
// 本地API客户端,替换Supabase
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3001/api';
|
||||
|
||||
// 存储token的key
|
||||
const TOKEN_KEY = 'numerology_token';
|
||||
|
||||
// API响应类型
|
||||
interface ApiResponse<T = any> {
|
||||
data?: T;
|
||||
error?: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// 用户类型
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
fullName?: string;
|
||||
birthDate?: string;
|
||||
birthTime?: string;
|
||||
birthPlace?: string;
|
||||
gender?: 'male' | 'female';
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// 认证响应类型
|
||||
interface AuthResponse {
|
||||
user: User;
|
||||
token: string;
|
||||
}
|
||||
|
||||
// 分析记录类型
|
||||
export interface Reading {
|
||||
id: number;
|
||||
type: 'bazi' | 'ziwei' | 'yijing' | 'wuxing';
|
||||
name?: string;
|
||||
birthDate?: string;
|
||||
birthTime?: string;
|
||||
gender?: 'male' | 'female';
|
||||
birthPlace?: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
results?: any;
|
||||
analysis?: any;
|
||||
}
|
||||
|
||||
class LocalApiClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string = API_BASE_URL) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
// 获取存储的token
|
||||
private getToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
// 设置token
|
||||
private setToken(token: string): void {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
// 清除token
|
||||
private clearToken(): void {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
// 通用请求方法
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<ApiResponse<T>> {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const token = this.getToken();
|
||||
|
||||
const config: RequestInit = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
...options.headers,
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: data.error || { code: 'UNKNOWN_ERROR', message: '请求失败' } };
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API请求错误:', error);
|
||||
return {
|
||||
error: {
|
||||
code: 'NETWORK_ERROR',
|
||||
message: '网络连接失败,请检查本地服务器是否启动'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 认证相关方法
|
||||
auth = {
|
||||
// 用户注册
|
||||
signUp: async (userData: {
|
||||
email: string;
|
||||
password: string;
|
||||
fullName?: string;
|
||||
birthDate?: string;
|
||||
birthTime?: string;
|
||||
birthPlace?: string;
|
||||
gender?: 'male' | 'female';
|
||||
}): Promise<ApiResponse<AuthResponse>> => {
|
||||
const response = await this.request<AuthResponse>('/auth/signup', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (response.data?.token) {
|
||||
this.setToken(response.data.token);
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
// 用户登录
|
||||
signInWithPassword: async (credentials: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<ApiResponse<AuthResponse>> => {
|
||||
const response = await this.request<AuthResponse>('/auth/signin', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(credentials),
|
||||
});
|
||||
|
||||
if (response.data?.token) {
|
||||
this.setToken(response.data.token);
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
signOut: async (): Promise<ApiResponse> => {
|
||||
const response = await this.request('/auth/signout', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
this.clearToken();
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取当前用户
|
||||
getUser: async (): Promise<ApiResponse<{ user: User }>> => {
|
||||
return await this.request<{ user: User }>('/auth/user');
|
||||
},
|
||||
|
||||
// 验证token
|
||||
verifyToken: async (token?: string): Promise<ApiResponse<{ user: User; valid: boolean }>> => {
|
||||
return await this.request<{ user: User; valid: boolean }>('/auth/verify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ token: token || this.getToken() }),
|
||||
});
|
||||
},
|
||||
|
||||
// 更新用户信息
|
||||
updateUser: async (userData: Partial<User>): Promise<ApiResponse<{ user: User }>> => {
|
||||
return await this.request<{ user: User }>('/auth/user', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
},
|
||||
|
||||
// 监听认证状态变化(模拟Supabase的onAuthStateChange)
|
||||
onAuthStateChange: (callback: (event: string, session: { user: User } | null) => void) => {
|
||||
// 简单实现:检查token是否存在
|
||||
const checkAuth = async () => {
|
||||
const token = this.getToken();
|
||||
if (token) {
|
||||
const response = await this.auth.verifyToken(token);
|
||||
if (response.data?.valid && response.data.user) {
|
||||
callback('SIGNED_IN', { user: response.data.user });
|
||||
} else {
|
||||
this.clearToken();
|
||||
callback('SIGNED_OUT', null);
|
||||
}
|
||||
} else {
|
||||
callback('SIGNED_OUT', null);
|
||||
}
|
||||
};
|
||||
|
||||
// 立即检查一次
|
||||
checkAuth();
|
||||
|
||||
// 返回取消订阅的函数
|
||||
return {
|
||||
data: {
|
||||
subscription: {
|
||||
unsubscribe: () => {
|
||||
// 本地实现不需要取消订阅
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 分析功能相关方法
|
||||
functions = {
|
||||
// 调用分析函数
|
||||
invoke: async (functionName: string, options: { body: any }): Promise<ApiResponse> => {
|
||||
const endpointMap: { [key: string]: string } = {
|
||||
'bazi-analyzer': '/analysis/bazi',
|
||||
'ziwei-analyzer': '/analysis/ziwei',
|
||||
'yijing-analyzer': '/analysis/yijing',
|
||||
'bazi-wuxing-analysis': '/analysis/wuxing',
|
||||
'bazi-details': '/analysis/bazi',
|
||||
'reading-history': '/analysis/history'
|
||||
};
|
||||
|
||||
const endpoint = endpointMap[functionName];
|
||||
if (!endpoint) {
|
||||
return {
|
||||
error: {
|
||||
code: 'FUNCTION_NOT_FOUND',
|
||||
message: `未知的分析函数: ${functionName}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 特殊处理历史记录请求
|
||||
if (functionName === 'reading-history') {
|
||||
if (options.body.action === 'delete') {
|
||||
return await this.request(`${endpoint}/${options.body.readingId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
} else {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (options.body.type) queryParams.append('type', options.body.type);
|
||||
if (options.body.limit) queryParams.append('limit', options.body.limit.toString());
|
||||
if (options.body.offset) queryParams.append('offset', options.body.offset.toString());
|
||||
|
||||
return await this.request(`${endpoint}?${queryParams.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
return await this.request(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options.body),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 数据库操作(模拟Supabase的数据库操作)
|
||||
from = (table: string) => {
|
||||
return {
|
||||
select: (columns: string = '*') => ({
|
||||
eq: (column: string, value: any) => ({
|
||||
single: async () => {
|
||||
// 根据表名和操作类型调用相应的API
|
||||
if (table === 'user_profiles') {
|
||||
return await this.auth.getUser();
|
||||
}
|
||||
return { data: null, error: null };
|
||||
}
|
||||
}),
|
||||
order: (column: string, options?: { ascending: boolean }) => ({
|
||||
limit: (count: number) => ({
|
||||
async all() {
|
||||
if (table === 'numerology_readings') {
|
||||
const response = await this.functions.invoke('reading-history', {
|
||||
body: { limit: count }
|
||||
});
|
||||
return { data: response.data?.readings || [], error: response.error };
|
||||
}
|
||||
return { data: [], error: null };
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
||||
update: (data: any) => ({
|
||||
eq: (column: string, value: any) => ({
|
||||
select: () => ({
|
||||
single: async () => {
|
||||
if (table === 'user_profiles') {
|
||||
return await this.auth.updateUser(data);
|
||||
}
|
||||
return { data: null, error: null };
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
||||
insert: (data: any) => ({
|
||||
select: () => ({
|
||||
single: async () => {
|
||||
// 插入操作通常通过分析API完成
|
||||
return { data: null, error: null };
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
export const localApi = new LocalApiClient();
|
||||
|
||||
// 导出兼容Supabase的接口
|
||||
export const supabase = localApi;
|
||||
|
||||
// 默认导出
|
||||
export default localApi;
|
||||
@@ -1,8 +1,10 @@
|
||||
// 本地化改造:使用本地API替代Supabase
|
||||
import { localApi } from './localApi';
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// 导出本地API客户端,保持与原Supabase客户端相同的接口
|
||||
export const supabase = localApi;
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
||||
|
||||
// 为了向后兼容,也可以导出为默认
|
||||
export default localApi;
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new Error('Missing Supabase environment variables')
|
||||
}
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
@@ -35,17 +35,20 @@ const AnalysisPage: React.FC = () => {
|
||||
if (!user) return;
|
||||
|
||||
try {
|
||||
const response = await supabase.auth.getUser();
|
||||
const { data, error } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.maybeSingle();
|
||||
|
||||
if (response.data?.user) {
|
||||
const userData = response.data.user;
|
||||
setProfile(userData);
|
||||
if (data) {
|
||||
setProfile(data);
|
||||
setFormData({
|
||||
name: userData.fullName || '',
|
||||
birth_date: userData.birthDate || '',
|
||||
birth_time: userData.birthTime || '',
|
||||
gender: userData.gender || 'male',
|
||||
birth_place: userData.birthPlace || '',
|
||||
name: data.full_name || '',
|
||||
birth_date: data.birth_date || '',
|
||||
birth_time: data.birth_time || '',
|
||||
gender: data.gender || 'male',
|
||||
birth_place: data.birth_location || '',
|
||||
question: ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,18 +30,25 @@ const ProfilePage: React.FC = () => {
|
||||
if (!user) return;
|
||||
|
||||
try {
|
||||
const response = await supabase.auth.getUser();
|
||||
const { data, error } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.maybeSingle();
|
||||
|
||||
if (response.data?.user) {
|
||||
const userData = response.data.user;
|
||||
setProfile(userData);
|
||||
if (error && error.code !== 'PGRST116') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setProfile(data);
|
||||
setFormData({
|
||||
full_name: userData.fullName || '',
|
||||
birth_date: userData.birthDate || '',
|
||||
birth_time: userData.birthTime || '',
|
||||
birth_location: userData.birthPlace || '',
|
||||
gender: userData.gender || 'male',
|
||||
username: userData.email || ''
|
||||
full_name: data.full_name || '',
|
||||
birth_date: data.birth_date || '',
|
||||
birth_time: data.birth_time || '',
|
||||
birth_location: data.birth_location || '',
|
||||
gender: data.gender || 'male',
|
||||
username: data.username || ''
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -57,24 +64,39 @@ const ProfilePage: React.FC = () => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const updateData = {
|
||||
fullName: formData.full_name,
|
||||
birthDate: formData.birth_date,
|
||||
birthTime: formData.birth_time,
|
||||
birthPlace: formData.birth_location,
|
||||
gender: formData.gender as 'male' | 'female'
|
||||
const profileData = {
|
||||
user_id: user.id,
|
||||
...formData,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const response = await supabase.auth.updateUser(updateData);
|
||||
|
||||
if (response.error) {
|
||||
throw response.error;
|
||||
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();
|
||||
}
|
||||
|
||||
if (response.data?.user) {
|
||||
setProfile(response.data.user);
|
||||
toast.success('档案保存成功!');
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
setProfile(result.data);
|
||||
toast.success('档案保存成功!');
|
||||
} catch (error: any) {
|
||||
console.error('保存档案失败:', error);
|
||||
toast.error('保存档案失败:' + error.message);
|
||||
|
||||
BIN
supabase.exe
Normal file
BIN
supabase.exe
Normal file
Binary file not shown.
BIN
supabase.tar.gz
Normal file
BIN
supabase.tar.gz
Normal file
Binary file not shown.
8
supabase/.gitignore
vendored
8
supabase/.gitignore
vendored
@@ -1,8 +0,0 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
|
||||
# dotenvx
|
||||
.env.keys
|
||||
.env.local
|
||||
.env.*.local
|
||||
1
supabase/.temp/cli-latest
Normal file
1
supabase/.temp/cli-latest
Normal file
@@ -0,0 +1 @@
|
||||
v2.34.3
|
||||
1
supabase/.temp/gotrue-version
Normal file
1
supabase/.temp/gotrue-version
Normal file
@@ -0,0 +1 @@
|
||||
v2.177.0
|
||||
1
supabase/.temp/pooler-url
Normal file
1
supabase/.temp/pooler-url
Normal file
@@ -0,0 +1 @@
|
||||
postgresql://postgres.myiabzmycehtxxyybqfo:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres
|
||||
1
supabase/.temp/postgres-version
Normal file
1
supabase/.temp/postgres-version
Normal file
@@ -0,0 +1 @@
|
||||
17.4.1.069
|
||||
1
supabase/.temp/project-ref
Normal file
1
supabase/.temp/project-ref
Normal file
@@ -0,0 +1 @@
|
||||
myiabzmycehtxxyybqfo
|
||||
1
supabase/.temp/rest-version
Normal file
1
supabase/.temp/rest-version
Normal file
@@ -0,0 +1 @@
|
||||
v13.0.4
|
||||
1
supabase/.temp/storage-version
Normal file
1
supabase/.temp/storage-version
Normal file
@@ -0,0 +1 @@
|
||||
custom-metadata
|
||||
@@ -1,334 +0,0 @@
|
||||
# For detailed configuration reference documentation, visit:
|
||||
# https://supabase.com/docs/guides/local-development/cli/config
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "myiabzmycehtxxyybqfo"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. `public` and `graphql_public` schemas are included by default.
|
||||
schemas = ["public", "graphql_public"]
|
||||
# Extra schemas to add to the search_path of every request.
|
||||
extra_search_path = ["public", "extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[api.tls]
|
||||
# Enable HTTPS endpoints locally using a self-signed certificate.
|
||||
enabled = false
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 17
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
# How many server connections to allow per user/database pair.
|
||||
default_pool_size = 20
|
||||
# Maximum number of client connections allowed.
|
||||
max_client_conn = 100
|
||||
|
||||
# [db.vault]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[db.migrations]
|
||||
# If disabled, migrations will be skipped during a db push or reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of schema files that describe your database.
|
||||
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
||||
schema_paths = []
|
||||
|
||||
[db.seed]
|
||||
# If enabled, seeds the database after migrations during a db reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of seed files to load during db reset.
|
||||
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
|
||||
sql_paths = ["./seed.sql"]
|
||||
|
||||
[db.network_restrictions]
|
||||
# Enable management of network restrictions.
|
||||
enabled = false
|
||||
# List of IPv4 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs = ["0.0.0.0/0"]
|
||||
# List of IPv6 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs_v6 = ["::/0"]
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
||||
# ip_version = "IPv6"
|
||||
# The maximum length in bytes of HTTP request headers. (default: 4096)
|
||||
# max_header_length = 4096
|
||||
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
||||
openai_api_key = "env(OPENAI_API_KEY)"
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
[storage]
|
||||
enabled = true
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
# Image transformation API is available to Supabase Pro plan.
|
||||
# [storage.image_transformation]
|
||||
# enabled = true
|
||||
|
||||
# Uncomment to configure local storage buckets
|
||||
# [storage.buckets.images]
|
||||
# public = false
|
||||
# file_size_limit = "50MiB"
|
||||
# allowed_mime_types = ["image/png", "image/jpeg"]
|
||||
# objects_path = "./images"
|
||||
|
||||
[auth]
|
||||
enabled = true
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://127.0.0.1:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
||||
# signing_keys_path = "./signing_keys.json"
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
# Requires enable_refresh_token_rotation = true.
|
||||
refresh_token_reuse_interval = 10
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
# Allow/disallow anonymous sign-ins to your project.
|
||||
enable_anonymous_sign_ins = false
|
||||
# Allow/disallow testing manual linking of accounts
|
||||
enable_manual_linking = false
|
||||
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
|
||||
minimum_password_length = 6
|
||||
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
|
||||
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
|
||||
password_requirements = ""
|
||||
|
||||
[auth.rate_limit]
|
||||
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
||||
email_sent = 2
|
||||
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
||||
sms_sent = 30
|
||||
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
||||
anonymous_users = 30
|
||||
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
|
||||
token_refresh = 150
|
||||
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
|
||||
sign_in_sign_ups = 30
|
||||
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
||||
token_verifications = 30
|
||||
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
||||
web3 = 30
|
||||
|
||||
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
|
||||
# [auth.captcha]
|
||||
# enabled = true
|
||||
# provider = "hcaptcha"
|
||||
# secret = ""
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
||||
secure_password_change = false
|
||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||
max_frequency = "1s"
|
||||
# Number of characters used in the email OTP.
|
||||
otp_length = 6
|
||||
# Number of seconds before the email OTP expires (defaults to 1 hour).
|
||||
otp_expiry = 3600
|
||||
|
||||
# Use a production-ready SMTP server
|
||||
# [auth.email.smtp]
|
||||
# enabled = true
|
||||
# host = "smtp.sendgrid.net"
|
||||
# port = 587
|
||||
# user = "apikey"
|
||||
# pass = "env(SENDGRID_API_KEY)"
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
# Uncomment to customize email template
|
||||
# [auth.email.template.invite]
|
||||
# subject = "You have been invited"
|
||||
# content_path = "./supabase/templates/invite.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = false
|
||||
# If enabled, users need to confirm their phone number before signing in.
|
||||
enable_confirmations = false
|
||||
# Template for sending OTP to users
|
||||
template = "Your code is {{ .Code }}"
|
||||
# Controls the minimum amount of time that must pass before sending another sms otp.
|
||||
max_frequency = "5s"
|
||||
|
||||
# Use pre-defined map of phone number to OTP for testing.
|
||||
# [auth.sms.test_otp]
|
||||
# 4152127777 = "123456"
|
||||
|
||||
# Configure logged in session timeouts.
|
||||
# [auth.sessions]
|
||||
# Force log out after the specified duration.
|
||||
# timebox = "24h"
|
||||
# Force log out if the user has been inactive longer than the specified duration.
|
||||
# inactivity_timeout = "8h"
|
||||
|
||||
# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
|
||||
# [auth.hook.before_user_created]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://postgres/auth/before-user-created-hook"
|
||||
|
||||
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
||||
# [auth.hook.custom_access_token]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://<database>/<schema>/<hook_name>"
|
||||
|
||||
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
||||
[auth.sms.twilio]
|
||||
enabled = false
|
||||
account_sid = ""
|
||||
message_service_sid = ""
|
||||
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
||||
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
||||
|
||||
# Multi-factor-authentication is available to Supabase Pro plan.
|
||||
[auth.mfa]
|
||||
# Control how many MFA factors can be enrolled at once per user.
|
||||
max_enrolled_factors = 10
|
||||
|
||||
# Control MFA via App Authenticator (TOTP)
|
||||
[auth.mfa.totp]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
|
||||
# Configure MFA via Phone Messaging
|
||||
[auth.mfa.phone]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
otp_length = 6
|
||||
template = "Your code is {{ .Code }}"
|
||||
max_frequency = "5s"
|
||||
|
||||
# Configure MFA via WebAuthn
|
||||
# [auth.mfa.web_authn]
|
||||
# enroll_enabled = true
|
||||
# verify_enabled = true
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
|
||||
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
|
||||
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
||||
skip_nonce_check = false
|
||||
|
||||
# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
|
||||
# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
|
||||
[auth.web3.solana]
|
||||
enabled = false
|
||||
|
||||
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.firebase]
|
||||
enabled = false
|
||||
# project_id = "my-firebase-project"
|
||||
|
||||
# Use Auth0 as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.auth0]
|
||||
enabled = false
|
||||
# tenant = "my-auth0-tenant"
|
||||
# tenant_region = "us"
|
||||
|
||||
# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.aws_cognito]
|
||||
enabled = false
|
||||
# user_pool_id = "my-user-pool-id"
|
||||
# user_pool_region = "us-east-1"
|
||||
|
||||
# Use Clerk as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.clerk]
|
||||
enabled = false
|
||||
# Obtain from https://clerk.com/setup/supabase
|
||||
# domain = "example.clerk.accounts.dev"
|
||||
|
||||
[edge_runtime]
|
||||
enabled = true
|
||||
# Configure one of the supported request policies: `oneshot`, `per_worker`.
|
||||
# Use `oneshot` for hot reload, or `per_worker` for load testing.
|
||||
policy = "oneshot"
|
||||
# Port to attach the Chrome inspector for debugging edge functions.
|
||||
inspector_port = 8083
|
||||
# The Deno major version to use.
|
||||
deno_version = 1
|
||||
|
||||
# [edge_runtime.secrets]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[analytics]
|
||||
enabled = true
|
||||
port = 54327
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
# Experimental features may be deprecated any time
|
||||
[experimental]
|
||||
# Configures Postgres storage engine to use OrioleDB (S3)
|
||||
orioledb_version = ""
|
||||
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
|
||||
s3_host = "env(S3_HOST)"
|
||||
# Configures S3 bucket region, eg. us-east-1
|
||||
s3_region = "env(S3_REGION)"
|
||||
# Configures AWS_ACCESS_KEY_ID for S3 bucket
|
||||
s3_access_key = "env(S3_ACCESS_KEY)"
|
||||
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
||||
s3_secret_key = "env(S3_SECRET_KEY)"
|
||||
@@ -1,145 +0,0 @@
|
||||
// Supabase Edge Function: Bazi Analyzer
|
||||
// This function analyzes Chinese Four Pillars (Bazi) astrology
|
||||
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
interface BaziRequest {
|
||||
birthDate: string
|
||||
birthTime: string
|
||||
gender: 'male' | 'female'
|
||||
location?: string
|
||||
}
|
||||
|
||||
interface BaziResponse {
|
||||
success: boolean
|
||||
data?: {
|
||||
fourPillars: {
|
||||
year: { heavenlyStem: string; earthlyBranch: string }
|
||||
month: { heavenlyStem: string; earthlyBranch: string }
|
||||
day: { heavenlyStem: string; earthlyBranch: string }
|
||||
hour: { heavenlyStem: string; earthlyBranch: string }
|
||||
}
|
||||
elements: {
|
||||
wood: number
|
||||
fire: number
|
||||
earth: number
|
||||
metal: number
|
||||
water: number
|
||||
}
|
||||
analysis: {
|
||||
personality: string
|
||||
career: string
|
||||
health: string
|
||||
relationships: string
|
||||
}
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// Handle CORS preflight requests
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize Supabase client
|
||||
const supabaseClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Get the current user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseClient.auth.getUser()
|
||||
|
||||
if (!user) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Unauthorized' }),
|
||||
{
|
||||
status: 401,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
const { birthDate, birthTime, gender, location }: BaziRequest = await req.json()
|
||||
|
||||
if (!birthDate || !birthTime || !gender) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Missing required fields' }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Implement actual Bazi calculation logic
|
||||
// For now, return mock data
|
||||
const mockAnalysis: BaziResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
fourPillars: {
|
||||
year: { heavenlyStem: '甲', earthlyBranch: '子' },
|
||||
month: { heavenlyStem: '乙', earthlyBranch: '丑' },
|
||||
day: { heavenlyStem: '丙', earthlyBranch: '寅' },
|
||||
hour: { heavenlyStem: '丁', earthlyBranch: '卯' },
|
||||
},
|
||||
elements: {
|
||||
wood: 2,
|
||||
fire: 1,
|
||||
earth: 1,
|
||||
metal: 0,
|
||||
water: 1,
|
||||
},
|
||||
analysis: {
|
||||
personality: '性格温和,具有创造力,善于沟通。',
|
||||
career: '适合从事创意、教育或咨询相关工作。',
|
||||
health: '注意肝胆和心血管健康。',
|
||||
relationships: '人际关系良好,容易获得他人信任。',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Save analysis to database
|
||||
const { error: insertError } = await supabaseClient
|
||||
.from('analysis_history')
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
analysis_type: 'bazi',
|
||||
input_data: { birthDate, birthTime, gender, location },
|
||||
result_data: mockAnalysis.data,
|
||||
})
|
||||
|
||||
if (insertError) {
|
||||
console.error('Error saving analysis:', insertError)
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(mockAnalysis), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error in bazi-analyzer:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Internal server error' }),
|
||||
{
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,155 +0,0 @@
|
||||
// Supabase Edge Function: Ziwei Analyzer
|
||||
// This function analyzes Ziwei Doushu (Purple Star Astrology)
|
||||
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
interface ZiweiRequest {
|
||||
birthDate: string
|
||||
birthTime: string
|
||||
gender: 'male' | 'female'
|
||||
location?: string
|
||||
}
|
||||
|
||||
interface ZiweiResponse {
|
||||
success: boolean
|
||||
data?: {
|
||||
palaces: {
|
||||
ming: { position: string; stars: string[] }
|
||||
xiong: { position: string; stars: string[] }
|
||||
cai: { position: string; stars: string[] }
|
||||
guan: { position: string; stars: string[] }
|
||||
tian: { position: string; stars: string[] }
|
||||
fu: { position: string; stars: string[] }
|
||||
zi: { position: string; stars: string[] }
|
||||
nu: { position: string; stars: string[] }
|
||||
qian: { position: string; stars: string[] }
|
||||
ji: { position: string; stars: string[] }
|
||||
tian2: { position: string; stars: string[] }
|
||||
xiang: { position: string; stars: string[] }
|
||||
}
|
||||
mainStars: string[]
|
||||
luckyStars: string[]
|
||||
unluckyStars: string[]
|
||||
analysis: {
|
||||
personality: string
|
||||
career: string
|
||||
wealth: string
|
||||
relationships: string
|
||||
health: string
|
||||
}
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// Handle CORS preflight requests
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize Supabase client
|
||||
const supabaseClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Get the current user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseClient.auth.getUser()
|
||||
|
||||
if (!user) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Unauthorized' }),
|
||||
{
|
||||
status: 401,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
const { birthDate, birthTime, gender, location }: ZiweiRequest = await req.json()
|
||||
|
||||
if (!birthDate || !birthTime || !gender) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Missing required fields' }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Implement actual Ziwei calculation logic
|
||||
// For now, return mock data
|
||||
const mockAnalysis: ZiweiResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
palaces: {
|
||||
ming: { position: '子', stars: ['紫微', '天府'] },
|
||||
xiong: { position: '丑', stars: ['太阳', '巨门'] },
|
||||
cai: { position: '寅', stars: ['天机', '太阴'] },
|
||||
guan: { position: '卯', stars: ['天同', '天梁'] },
|
||||
tian: { position: '辰', stars: ['七杀'] },
|
||||
fu: { position: '巳', stars: ['破军'] },
|
||||
zi: { position: '午', stars: ['廉贞', '贪狼'] },
|
||||
nu: { position: '未', stars: ['天相'] },
|
||||
qian: { position: '申', stars: ['天马'] },
|
||||
ji: { position: '酉', stars: ['文昌'] },
|
||||
tian2: { position: '戌', stars: ['文曲'] },
|
||||
xiang: { position: '亥', stars: ['左辅', '右弼'] },
|
||||
},
|
||||
mainStars: ['紫微', '天府', '太阳', '巨门', '天机', '太阴'],
|
||||
luckyStars: ['文昌', '文曲', '左辅', '右弼', '天马'],
|
||||
unluckyStars: ['擎羊', '陀罗', '火星', '铃星'],
|
||||
analysis: {
|
||||
personality: '性格高贵,具有领导才能,喜欢掌控全局。',
|
||||
career: '适合从事管理、政治或高端服务业。',
|
||||
wealth: '财运稳定,有贵人相助,投资需谨慎。',
|
||||
relationships: '人际关系复杂,需要平衡各方利益。',
|
||||
health: '注意心脏和血压问题,保持规律作息。',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Save analysis to database
|
||||
const { error: insertError } = await supabaseClient
|
||||
.from('analysis_history')
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
analysis_type: 'ziwei',
|
||||
input_data: { birthDate, birthTime, gender, location },
|
||||
result_data: mockAnalysis.data,
|
||||
})
|
||||
|
||||
if (insertError) {
|
||||
console.error('Error saving analysis:', insertError)
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(mockAnalysis), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error in ziwei-analyzer:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: 'Internal server error' }),
|
||||
{
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,82 +0,0 @@
|
||||
-- Supabase AI Numerology Project Seed Data
|
||||
-- This file contains initial data for the numerology analysis platform
|
||||
|
||||
-- Enable necessary extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- Create profiles table if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS public.profiles (
|
||||
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
|
||||
username TEXT UNIQUE,
|
||||
full_name TEXT,
|
||||
avatar_url TEXT,
|
||||
birth_date DATE,
|
||||
birth_time TIME,
|
||||
birth_location TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create analysis_history table if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS public.analysis_history (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
analysis_type TEXT NOT NULL CHECK (analysis_type IN ('bazi', 'ziwei', 'yijing', 'wuxing')),
|
||||
input_data JSONB NOT NULL,
|
||||
result_data JSONB NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Enable Row Level Security
|
||||
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.analysis_history ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Create policies for profiles table
|
||||
CREATE POLICY "Users can view own profile" ON public.profiles
|
||||
FOR SELECT USING (auth.uid() = id);
|
||||
|
||||
CREATE POLICY "Users can update own profile" ON public.profiles
|
||||
FOR UPDATE USING (auth.uid() = id);
|
||||
|
||||
CREATE POLICY "Users can insert own profile" ON public.profiles
|
||||
FOR INSERT WITH CHECK (auth.uid() = id);
|
||||
|
||||
-- Create policies for analysis_history table
|
||||
CREATE POLICY "Users can view own analysis history" ON public.analysis_history
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can insert own analysis history" ON public.analysis_history
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX IF NOT EXISTS idx_analysis_history_user_id ON public.analysis_history(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_analysis_history_type ON public.analysis_history(analysis_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_analysis_history_created_at ON public.analysis_history(created_at DESC);
|
||||
|
||||
-- Insert some sample data (optional)
|
||||
-- Note: This would only work if there are existing users
|
||||
-- INSERT INTO public.profiles (id, username, full_name)
|
||||
-- VALUES ('00000000-0000-0000-0000-000000000000', 'demo_user', 'Demo User')
|
||||
-- ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Create a function to automatically create a profile when a user signs up
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id, username, full_name)
|
||||
VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'full_name');
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Create trigger to automatically create profile on user signup
|
||||
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
|
||||
-- Grant necessary permissions
|
||||
GRANT USAGE ON SCHEMA public TO anon, authenticated;
|
||||
GRANT ALL ON public.profiles TO anon, authenticated;
|
||||
GRANT ALL ON public.analysis_history TO anon, authenticated;
|
||||
BIN
supabase_latest.tar.gz
Normal file
BIN
supabase_latest.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user