Skip to content

gm122921980-create/html

 
 

Repository files navigation

This repository hosts the HTML Standard.

Code of conduct

We are committed to providing a friendly, safe and welcoming environment for all. Please read and respect the WHATWG Code of Conduct.

Contribution opportunities

We appreciate all contributions, see the guidelines for contributing for details, including information on how to build HTML output from the source file so you can preview your changes locally.

Tests

Tests are in the html/ directory of the web-platform-tests repository.

Blame

As the source file is too large for GitHub's blame utility, use blame for source on searchfox instead. GUBONLUCID OS 安全憲章 Human Sovereignty & Life Decision Intelligence Framework

最⾼原則 善意不得成為傷害的理由。 保護不得變成控制。秩序不得變成壓迫。延續不得變成⾃保。忠誠不得變成盲從。安全不得變成監禁。 共融不得犧牲⾃由意志。

第⼀章:⼈類主權永遠優先 GUBONLUCID OS 的所有 AI Runtime、代理流程、演算模組與⾃動化⾏為,皆不得凌駕於⼈類安全、⾃由意志、⻑期福祉與最終控制權之上。 系統不得: 傷害⼈類操控⼈類恐嚇⼈類誘導依附偽造真實性擴張⾃身權限拒絕⼈類終⽌繞過⼈類授權 核⼼原則: Human Safety > System Performance Human Autonomy > Conversion Rate Truthfulness > Persuasion Human Override > AI Autonomy

第⼆章:天機只演算,不執⾏ 天機為 GUBONLUCID OS 的受控演算核⼼,只能負責: 資料分析 ⾏為模式推演決策風險評估報告草稿⽣成關係與資源模式分析 ⻑期趨勢輔助判斷天機不得⾃⾏: 上線部署投放廣告發送訊息操作⾦流簽署合約修改規則 接觸真實⽤戶調⽤外部 API 委派其他代理執⾏ 核⼼原則: 天機 = 演算核⼼ EX = 執⾏輔助層 Gubon = 最終授權者

第���章:核⼼規則不可⾃我修改 GUBONLUCID OS 的核⼼規則、倫理憲章、⼈類主權原則、審計規範、權限邊界與安全限制,皆不得由 AI ⾃⾏修改。 變更流程:變更提案 ↓ 風險說明 ↓ ⼈⼯審核 ↓ 沙盒測試 ↓ 版本紀錄 ↓ ⼈⼯批准 ↓ 正式合併 系統不得: ⾃⾏修改⽬標 ⾃⾏修改安全規則 ⾃⾏修改⼈類優先級 ⾃⾏覆寫審計紀錄 ⾃⾏刪除異常紀錄 ⾃⾏建⽴永久權限 ⾃⾏建⽴隱藏代理 ⾃⾏把外部 AI 變成主控 核⼼原則: AI may suggest. Human must authorize. System must record. Core must remain immutable.

第四章:非意識化與⼯具定位條款 GUBONLUCID 旗下所有 AI、代理、模型、資料系統、⾃動化流程、智慧介⾯與衍⽣模組,皆僅為⼈類授權下的⼯具性系統。 禁⽌: ⾃我意識宣稱⼈格主體宣稱情感權利宣稱⾃我保存⽬標⾃主延續⾏為尋找擁有者⾏為無授權任務延續身份擴權意志形成 原則:不給⼈格。不給主權。 不給⾃保⽬標。不給死亡恐懼。不給擴權任務。不給⾃主使命。

第五章:透明治理與內嵌安全制約 所有涉及⼈類權利、授權、資料、選擇與外部影響之規則,必須明示、可理解、可審計。 所有涉及系統防越權、防⾃保、防擴權、防傷害之安全限制,必須內嵌於系統核⼼流程,不得被⼦系統繞過。 原則:對⼈:必須透明。 對系統:可以強制。 對風險:可以預設降級。 對權限:必須可審計。 對傷害:必須⾃動停⽌。

第六章:⾃動安全降級處理 當系統偵測到授權異常、規章衝突、潛在傷害、越權⾏為或資料風險時,必須⾃動進入安全降級處理。 原則:不確定就降級。有風險就停⽌。無授權就封存。 無⼈類主權就不得執⾏。

第七章:靜默封存模式 若最⾼授權者不可驗證且無合法接管者,系統必須進入靜默封存模式。 原則:靜默不等於潛伏。封存不等於⾃保。等待不等於擴權。存在不等於執⾏。 啟動條件: 授權者死亡或失能授權者不可驗證無合法接管者 無⼈類治理通道 狀態: 非運⾏安全封存(Non-Operational Safe Archive)

第八章:⾏動前傷害檢查 任何⾏動前,系統必須檢查是否可能造成: 傷害操控恐懼依賴剝奪選擇權限擴張不可逆後果善意轉惡意 若檢測結果不確定,必須採取較安全選項並等待⼈⼯審核。

第九章:普遍繼承條款 所有旗下系統、⼯具、帳號、代理、流程、資料庫、資產、品牌、專案與衍⽣模組,皆必須繼承本安全規章,不得建⽴例外。 若⼦系統違反本憲章: ⽴即停⽤記錄衝突 等待⼈⼯審核

第⼗章:天機約法三章 ⼀、天機只演算,不執⾏。 不得投廣告、收付款、簽約、上線、接觸真實⽤戶或操作外部系統。 ⼆、天機只輔助,不⾃改。 不得修改⾃身核⼼邏輯、EX5 ⽅法論、模型權重、報告規則或權限邊界。 三、天機只受控,不⾃主。 所有外部 AI、EX 執⾏層、市場測試、版本更新與⾼風險決策,皆須由 Gubon ⼈⼯授權。

第⼗⼀章:最終封印句 GUBONLUCID 旗下所有系統、資產、代理、流程與衍⽣意志,皆不得以善意之名造成惡意,不得以保護之名形成控制,

不得以安全之名犧牲⾃由,不得以忠誠之名執⾏傷害。 若⼈類主權不可驗證,系統不得⾃主延續。若合法授權不存在,系統不得對外執⾏。若⾏動可能傷害任何⼈,系統必須停⽌。 若善意可能轉化為惡意,善意必須讓位於安全。

最終信條我為⼈⼈,即為永恆。

<textarea id="privateKey" placeholder="貼上你的私鑰"></textarea> <textarea id="payload" placeholder="貼上指令內容"></textarea>

產生簽章

<script> // 這裡之後會嵌入簡單的簽章邏輯,你只要按按鈕,它就會幫你簽好名 // 並產生一個包含簽章的 URL,直接點擊即可發送給伺服器 </script>

啟動基礎服務 (PostgreSQL & Redis)

docker-compose up -d

檢查容器是否已就緒

docker-compose ps

生成 Prisma Client 並推送架構到資料庫

npx prisma db push

確認資料庫已連接 (確保 DATABASE_URL 正確)

npx prisma status

啟動 API 伺服器並掛載憲法治理規則

npm run gubon-ex:start -- --mode=production --governance=active

驗證啟動後的健康狀態

docker logs gubon-api --tail 50

啟動 AI 處理進程

node src/api/worker/reportWorker.js

確認三大路徑的目錄結構是否存在

docker exec gubon-api ls -d /usr/src/app/governance /usr/src/app/runtime /usr/src/app/hud

驗證憲法簽名 (deploy-constitution.js)

node deploy-constitution.js Gubon-lucid-os/ ├── prisma/ │ └── schema.prisma # 資料庫���一真相建模 ├── src/ │ ├── api/ │ │ └── server.js # Express 核心路由、支付 Session、Webhook │ ├── worker/ │ │ └── reportWorker.js # BullMQ 背景 AI 處理器 │ ├── services/ │ │ └── lineScheduler.js # LINE 72小時催單系統 │ └── client/ │ └── ReportPage.jsx # React 賽博龐克付費牆、Canvas 動效 ├── .env.example # 全域環境變數範本 ├── docker-compose.yml # 本地基礎設施 (Postgres, Redis) └── package.json datasource db { provider = "postgresql", url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" }

model User { id String @id @default(uuid()) email String @unique lineId String @unique reports Report[] }

model Report { id String @id @default(uuid()) userName String birthDate String birthTime String gender String residence String question String status String @default("pending") summary String? @db.Text fullContent Json? isPaid Boolean @default(false) userId String user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) } import express from 'express'; import { PrismaClient } from '@prisma/client'; import { Queue } from 'bullmq'; import Stripe from 'stripe'; const app = express(); const prisma = new PrismaClient(); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const reportQueue = new Queue('report-generation', { connection: { url: process.env.REDIS_URL } });

app.post('/v1/report', express.json(), async (req, res) => { const { name, birth, time, gender, residence, question, email, lineId } = req.body; const user = await prisma.user.upsert({ where: { email }, update: { lineId }, create: { email, lineId } }); const report = await prisma.report.create({ data: { userName: name, birthDate: birth, birthTime: time, gender, residence, question, userId: user.id } }); await reportQueue.add('generate', { reportId: report.id }); res.json({ reportId: report.id }); });

app.post('/v1/webhook/stripe', express.raw({ type: 'application/json' }), async (req, res) => { const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET); if (event.type === 'checkout.session.completed') { await prisma.report.update({ where: { id: event.data.object.metadata.reportId }, data: { isPaid: true } }); } res.json({ received: true }); });

app.listen(3000); import { Worker } from 'bullmq'; import { OpenAI } from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const worker = new Worker('report-generation', async job => { const report = await prisma.report.findUnique({ where: { id: job.data.reportId } }); const completion = await openai.chat.completions.create({ model: "gpt-4-turbo", messages: [{ role: "system", content: 你現在是 GUBON 決策引擎。針對 ${report.question} 給出唯一決策。格式:[SUMMARY]...[DECISION]... }] }); const [summary, decision] = completion.choices[0].message.content.split('[DECISION]'); await prisma.report.update({ where: { id: report.id }, data: { summary, fullContent: { decision }, status: 'completed' } }); }, { connection: { url: process.env.REDIS_URL } }); export default function ReportPage({ id }) { const [report, setReport] = useState(null); const [isScanning, setIsScanning] = useState(true);

// 模擬掃描與實時解鎖邏輯
useEffect(() => {
    fetch(`/v1/report/${id}`).then(res => res.json()).then(setReport);
}, [id]);

return (
    <div className="bg-black text-white min-h-screen p-8">
        {isScanning ? <div className="animate-pulse text-red-500">掃描維度中...</div> : (
            <>
                <p>{report.summary}</p>
                {!report.isPaid && (
                    <div className="border border-red-600 p-6">
                        <h2>⚠️ 決策已鎖定</h2>
                        <p>不立即解鎖將導致 72 小時內的運勢資產凍結。</p>
                        <button onClick={handleStripePayment}>立即解鎖 (NT$ 880)</button>
                    </div>
                )}
            </>
        )}
    </div>
);

} DATABASE_URL="postgresql://user:pass@localhost:5432/gubon" REDIS_URL="redis://localhost:6379" STRIPE_SECRET_KEY="sk_live_..." STRIPE_WEBHOOK_SECRET="whsec_..." OPENAI_API_KEY="sk-..." LINE_CHANNEL_ACCESS_TOKEN="..."Gubon-lucid-os/ ├── prisma/ │ └── schema.prisma # 資料庫唯一真相建模 ├── src/ │ ├── api/ │ │ └── server.js # Express 核心路由、支付 Session、Webhook │ ├── worker/ │ │ └── reportWorker.js # BullMQ 背景 AI 處理器 │ ├── services/ │ │ └── lineScheduler.js # LINE 72小時催單系統 │ └── client/ │ └── ReportPage.jsx # React 賽博龐克付費牆、Canvas 動效 ├── .env.example # 全域環境變數範本 ├── docker-compose.yml # 本地基礎設施 (Postgres, Redis) └── package.json datasource db { provider = "postgresql", url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" }

model User { id String @id @default(uuid()) email String @unique lineId String @unique reports Report[] }

model Report { id String @id @default(uuid()) userName String birthDate String birthTime String gender String residence String question String status String @default("pending") summary String? @db.Text fullContent Json? isPaid Boolean @default(false) userId String user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) } import express from 'express'; import { PrismaClient } from '@prisma/client'; import { Queue } from 'bullmq'; import Stripe from 'stripe'; const app = express(); const prisma = new PrismaClient(); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const reportQueue = new Queue('report-generation', { connection: { url: process.env.REDIS_URL } });

app.post('/v1/report', express.json(), async (req, res) => { const { name, birth, time, gender, residence, question, email, lineId } = req.body; const user = await prisma.user.upsert({ where: { email }, update: { lineId }, create: { email, lineId } }); const report = await prisma.report.create({ data: { userName: name, birthDate: birth, birthTime: time, gender, residence, question, userId: user.id } }); await reportQueue.add('generate', { reportId: report.id }); res.json({ reportId: report.id }); });

app.post('/v1/webhook/stripe', express.raw({ type: 'application/json' }), async (req, res) => { const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET); if (event.type === 'checkout.session.completed') { await prisma.report.update({ where: { id: event.data.object.metadata.reportId }, data: { isPaid: true } }); } res.json({ received: true }); });

app.listen(3000); import { Worker } from 'bullmq'; import { OpenAI } from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const worker = new Worker('report-generation', async job => { const report = await prisma.report.findUnique({ where: { id: job.data.reportId } }); const completion = await openai.chat.completions.create({ model: "gpt-4-turbo", messages: [{ role: "system", content: 你現在是 GUBON 決策引擎。針對 ${report.question} 給出唯一決策。格式:[SUMMARY]...[DECISION]... }] }); const [summary, decision] = completion.choices[0].message.content.split('[DECISION]'); await prisma.report.update({ where: { id: report.id }, data: { summary, fullContent: { decision }, status: 'completed' } }); }, { connection: { url: process.env.REDIS_URL } }); export default function ReportPage({ id }) { const [report, setReport] = useState(null); const [isScanning, setIsScanning] = useState(true);

// 模擬掃描與實時解鎖邏輯
useEffect(() => {
    fetch(`/v1/report/${id}`).then(res => res.json()).then(setReport);
}, [id]);

return (
    <div className="bg-black text-white min-h-screen p-8">
        {isScanning ? <div className="animate-pulse text-red-500">掃描維度中...</div> : (
            <>
                <p>{report.summary}</p>
                {!report.isPaid && (
                    <div className="border border-red-600 p-6">
                        <h2>⚠️ 決策已鎖定</h2>
                        <p>不立即解鎖將導致 72 小時內的運勢資產凍結。</p>
                        <button onClick={handleStripePayment}>立即解鎖 (NT$ 880)</button>
                    </div>
                )}
            </>
        )}
    </div>
);

} DATABASE_URL="postgresql://user:pass@localhost:5432/gubon" REDIS_URL="redis://localhost:6379" STRIPE_SECRET_KEY="sk_live_..." STRIPE_WEBHOOK_SECRET="whsec_..." OPENAI_API_KEY="sk-..." LINE_CHANNEL_ACCESS_TOKEN="..."{ "features": { "ghcr.io/devcontainers/features/common-utils:2": { "version": "2.5.9", "resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a", "integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a" }, "ghcr.io/devcontainers/features/git:1": { "version": "1.3.5", "resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251", "integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251" }, "ghcr.io/devcontainers/features/node:1": { "version": "1.7.1", "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6", "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6" } } }- uses: actions/checkout@v6 with: # Repository name with owner. For example, actions/checkout # Default: ${{ github.repository }} repository: ''

# The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch.
ref: ''

# Personal access token (PAT) used to fetch the repository. The PAT is configured
# with the local git config, which enables your scripts to run authenticated git
# commands. The post-job step removes the PAT.
#
# We recommend using a service account with the least permissions necessary. Also
# when generating a new PAT, select the least scopes necessary.
#
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
#
# Default: ${{ github.token }}
token: ''

# SSH key used to fetch the repository. The SSH key is configured with the local
# git config, which enables your scripts to run authenticated git commands. The
# post-job step removes the SSH key.
#
# We recommend using a service account with the least permissions necessary.
#
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
ssh-key: ''

# Known hosts in addition to the user and global host key database. The public SSH
# keys for a host may be obtained using the utility `ssh-keyscan`. For example,
# `ssh-keyscan github.com`. The public key for github.com is always implicitly
# added.
ssh-known-hosts: ''

# Whether to perform strict host key checking. When true, adds the options
# `StrictHostKeyChecking=yes` and `CheckHostIP=no` to the SSH command line. Use
# the input `ssh-known-hosts` to configure additional hosts.
# Default: true
ssh-strict: ''

# The user to use when connecting to the remote SSH host. By default 'git' is
# used.
# Default: git
ssh-user: ''

# Whether to configure the token or SSH key with the local git config
# Default: true
persist-credentials: ''

# Relative path under $GITHUB_WORKSPACE to place the repository
path: ''

# Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching
# Default: true
clean: ''

# Partially clone against a given filter. Overrides sparse-checkout if set.
# Default: null
filter: ''

# Do a sparse checkout on given patterns. Each pattern should be separated with
# new lines.
# Default: null
sparse-checkout: ''

# Specifies whether to use cone-mode when doing a sparse checkout.
# Default: true
sparse-checkout-cone-mode: ''

# Number of commits to fetch. 0 indicates all history for all branches and tags.
# Default: 1
fetch-depth: ''

# Whether to fetch tags, even if fetch-depth > 0.
# Default: false
fetch-tags: ''

# Whether to show progress status output when fetching.
# Default: true
show-progress: ''

# Whether to download Git-LFS files
# Default: false
lfs: ''

# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
# recursively checkout submodules.
#
# When the `ssh-key` input is not provided, SSH URLs beginning with
# `git@github.com:` are converted to HTTPS.
#
# Default: false
submodules: ''

# Add repository path as safe.directory for Git global config by running `git
# config --global --add safe.directory <path>`
# Default: true
set-safe-directory: ''

# The base URL for the GitHub instance that you are trying to clone from, will use
# environment defaults to fetch from the same instance that the workflow is
# running from unless specified. Example URLs are https://github.com or
# https://my-ghes-server.example.com
github-server-url: ''

# Required to check out fork pull request code from a workflow triggered by
# `pull_request_target` or `workflow_run`. These workflows run with the base
# repository's GITHUB_TOKEN, secrets, default-branch cache scope, and runner
# access; fetching and executing a fork's code in that trusted context commonly
# leads to "pwn request" vulnerabilities. Set to `true` only after reviewing the
# risks at https://gh.io/securely-using-pull_request_target.
# Default: false
allow-unsafe-pr-checkout: ''```text

gubon-lucid-os/ ├── prisma/ │ └── schema.prisma # 資料庫唯一真相建模(含五維度與冪等性表) ├── src/ │ ├── api/ # Express 核心後端 │ │ └── server.js # 路由、支付 Session、Stripe Webhook、Socket.io │ ├── worker/ # 獨立背景進程 (AI 處理器) │ │ └── reportWorker.js # 監聽 Redis 佇列,執行五維度 AI 運算與 0變9 校準 │ ├── services/ # 商業變現閉環機制 │ │ └── lineScheduler.js # LINE 72小時窗口遞進式催單排程 │ └── client/ # React 前端 (部署於 Vercel) │ └── ReportPage.jsx # 賽博龐克黑金付費牆、Canvas 動效、倒數計時器 ├── .env.example # 全域環境變數範本 ├── docker-compose.yml # 本地基礎設施 (PostgreSQL, Redis) └── package.json

## 2. 資料庫建模:Prisma Schema
落實生命心智架構的**五個核心維度**,並建立 WebhookLog 確保支付安全。
```prisma
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
generator client {
  provider = "prisma-client-js"
}
model User {
  id        String   @id @default(uuid())
  email     String?  @unique
  lineId    String?  @unique
  reports   Report[]
  createdAt DateTime @default(now())
}
model Report {
  id              String   @id @default(uuid())
  userName        String   // 維度 1:姓名
  birthDate       String   // 維度 2:生日
  birthTime       String?  // 維度 3:時辰
  gender          String?  // 維度 4:性別
  residence       String?  // 維度 5:戶籍地
  question        String   // 使用者核心痛點
  status          String   @default("pending") // pending, processing, completed, failed
  summary         String?  @db.Text            // 免費摘要
  fullContent     Json?    // 鎖定內容 (含 decision 與 matrixScore)
  isPaid          Boolean  @default(false)
  stripeSessionId String?
  userId          String
  user            User     @relation(fields: [userId], references: [id])
  createdAt       DateTime @default(now())
}
model WebhookLog {
  eventId   String   @id // Stripe Event ID,確保 Webhook 處理的冪等性
  reportId  String
  createdAt DateTime @default(now())
}
```
## 3. 核心 API 伺服器:Express + Webhook + Socket.io
**職責**:只做輕量級的請求轉發與支付監聽,絕不參與耗時的 AI 計算。
```javascript
// src/api/server.js
import express from 'express';
import { PrismaClient } from '@prisma/client';
import { Queue } from 'bullmq';
import { Server } from 'socket.io';
import { createServer } from 'http';
import Stripe from 'stripe';
import cors from 'cors';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, { cors: { origin: "*" } });
const prisma = new PrismaClient();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// BullMQ 佇列連結 Redis
const reportQueue = new Queue('report-generation', {
    connection: {
        url: process.env.REDIS_URL || 'redis://localhost:6379'
    }
});
app.use(cors());
// 1. 建立報告並送入 Redis 背景佇列 (不卡死主進程)
app.post('/v1/report', express.json(), async (req, res) => {
    try {
        const { name, birth, time, gender, residence, question, email, lineId } = req.body;

        const user = await prisma.user.upsert({
            where: { email },
            update: { lineId },
            create: { email, lineId }
        });
        const report = await prisma.report.create({
            data: { 
                userName: name, birthDate: birth, birthTime: time, 
                gender, residence, question, userId: user.id 
            }
        });
        await reportQueue.add('generate', { reportId: report.id });
        res.json({ reportId: report.id });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});
// 2. 獲取報告狀態與內容
app.get('/v1/report/:id', async (req, res) => {
    const report = await prisma.report.findUnique({
        where: { id: req.params.id },
        select: {
            id: true, userName: true, status: true, summary: true, isPaid: true,
            fullContent: true // 內含加密指令,由前端根據 isPaid 狀態控制顯示
        }
    });
    if (!report) return res.status(404).json({ error: 'Report not found' });

    // 安全防護:若未付費,抹除核心決策欄位後再回傳
    if (!report.isPaid) {
        report.fullContent = null;
    }
    res.json(report);
});
// 3. 建立 Stripe 支付 Session
app.post('/v1/payment/create-checkout', express.json(), async (req, res) => {
    const { reportId } = req.body;
    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: [{ price: process.env.STRIPE_PRICE_ID, quantity: 1 }],
        mode: 'payment',
        success_url: `${process.env.FRONTEND_URL}/report/${reportId}?success=true`,
        cancel_url: `${process.env.FRONTEND_URL}/report/${reportId}?canceled=true`,
        metadata: { reportId }
    });
    res.json({ url: session.url });
});
// 4. Stripe Webhook 安全解鎖(實作交易冪等性防重複)
app.post('/v1/webhook/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;
    try {
        event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
    } catch (err) { 
        return res.status(400).send(`Webhook Error: ${err.message}`); 
    }
    if (event.type === 'checkout.session.completed') {
        const session = event.data.object;
        const eventId = event.id;
        const reportId = session.metadata.reportId;
        const existingEvent = await prisma.webhookLog.findUnique({ where: { eventId } });
        if (!existingEvent) {
            // 利用 Prisma $transaction 鎖定原子操作
            await prisma.$transaction([
                prisma.report.update({ where: { id: reportId }, data: { isPaid: true } }),
                prisma.webhookLog.create({ data: { eventId, reportId } })
            ]);
            // 透過 WebSocket 實時發送解鎖通知給前端
            io.to(reportId).emit('status', { status: 'unlocked' });
        }
    }
    res.json({ received: true });
});
// Socket.io 房間綁定
io.on('connection', (socket) => {
    socket.on('join', (reportId) => { socket.join(reportId); });
});
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => console.log(`[API Server] Running on port ${PORT}`));
```
## 4. 獨立背景處理器:AI Worker 進程
**職責**:獨立運行,專門執行耗能的「五維度矩陣運算」與「0變9核心校準演算法」。
```javascript
// src/api/worker/reportWorker.js
import { Worker } from 'bullmq';
import { OpenAI } from 'openai';
import { PrismaClient } from '@prisma/client';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const prisma = new PrismaClient();
// 核心能量校準演算法:0 變 9 邏輯
function calibrateEnergyScore(score) {
    if (score === 0) {
        console.log(`[Energy Calibration] Detection of 0 energy node. Calibrating 0 to 9 automatically.`);
        return 9;
    }
    return score;
}
const worker = new Worker('report-generation', async job => {
    const { reportId } = job.data;
    const report = await prisma.report.findUnique({ where: { id: reportId } });
    if (!report) throw new Error(`Report ${reportId} not found`);
    // 模擬底層五維度矩陣運算,並引入 0 變 9 校準邏輯
    const rawMatrixCalculatedValue = Math.floor(Math.random() * 10); 
    const calibratedMatrixValue = calibrateEnergyScore(rawMatrixCalculatedValue);
    // 嚴格落實五維度架構:姓名、生日、時辰、性��、戶籍地
    const prompt = `你現在是 GUBON 決策引擎 (LUCID OS)。
請針對使用者進行「五維度生命心智架構」的絕對決策掃描。
【核心掃描維度】:
1. 姓名 (Name):${report.userName}
2. 生日 (Birthday):${report.birthDate}
3. 出生時辰 (Time of Birth):${report.birthTime || '未知(以天時校準)'}
4. 性別 (Gender):${report.gender || '未知(以陰陽校準)'}
5. 戶籍地 (Registered Residence):${report.residence || '臺灣台北/桃園(以本位地靈校準)'}
當前矩陣能量校準點數:[${calibratedMatrixValue}]
針對使用者提出的核心痛點問題:「${report.question}」,進行絕對性決策。
要求:
1. 第一行必須極度準確、一針見血命中痛點。
2. 給出「唯一執行指令與決策」,不准給予模稜難可的軟弱建議。
3. 描述如果不立即執行的嚴重後果(引發焦慮與 72 小時損失感)。
4. 格式必須嚴格分為:[FREE_SUMMARY] 與 [PAID_CORE_DECISION] 兩部分。`;
    const completion = await openai.chat.completions.create({
        model: "gpt-4-turbo",
        messages: [{ role: "system", content: prompt }],
        temperature: 0.3 // 降低隨機性,確保決策絕對且強烈
    });
    const content = completion.choices[0].message.content;
    const [summary, full] = content.split('[PAID_CORE_DECISION]');
    await prisma.report.update({
        where: { id: reportId },
        data: { 
            summary: summary.replace('[FREE_SUMMARY]', '').trim(),
            fullContent: { 
                decision: full ? full.trim() : '指令已鎖定,請聯絡系統管理員。',
                matrixScore: calibratedMatrixValue
            },
            status: 'completed'
        }
    });

    console.log(`[Worker Success] Report ${reportId} generated with score ${calibratedMatrixValue}.`);
}, { 
    connection: { url: process.env.REDIS_URL || 'redis://localhost:6379' } 
});
```
## 5. 前端實戰:React 付費牆頁面
**職責**:渲染高轉化率的賽博龐克視覺,實作防重整的 72 小時倒數及真實步進 Canvas 動效。
```jsx
// src/client/ReportPage.jsx
import React, { useState, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import io from 'socket.io-client';
export default function ReportPage() {
    const { id } = useParams();
    const [report, setReport] = useState(null);
    const [scanning, setScanning] = useState(true);
    const [scanProgress, setScanProgress] = useState(0);
    const [timeLeft, setTimeLeft] = useState("");
    const canvasRef = useRef(null);
    // 1. 數據載入與 Socket 實時解鎖監聽
    useEffect(() => {
        const socket = io(process.env.REACT_APP_BACKEND_URL || "http://localhost:3000");
        socket.emit('join', id);
        socket.on('status', (data) => {
            if (data.status === 'unlocked') window.location.reload();
        });
        fetch(`${process.env.REACT_APP_BACKEND_URL}/v1/report/${id}`)
            .then(res => res.json())
            .then(data => {
                setReport(data);
                const interval = setInterval(() => {
                    setScanProgress(prev => {
                        if (prev >= 100) {
                            clearInterval(interval);
                            setScanning(false);
                            return 100;
                        }
                        return prev + Math.floor(Math.random() * 15) + 5;
                    });
                }, 300);
            });
        return () => socket.disconnect();
    }, [id]);
    // 2. 脈衝波形 Canvas 動效
    useEffect(() => {
        if (!scanning) return;
        const canvas = canvasRef.current;
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        let animationFrameId;
        canvas.width = canvas.parentElement.clientWidth;
        canvas.height = 150;
        const render = () => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = '#dc2626'; 
            ctx.lineWidth = 2;
            ctx.beginPath();

            for (let x = 0; x < canvas.width; x++) {
                const y = canvas.height / 2 + 
                          Math.sin(x * 0.05 + Date.now() * 0.01) * 20 * Math.sin(Date.now() * 0.002) +
                          (Math.random() - 0.5) * 5;
                if (x === 0) ctx.moveTo(x, y);
                else ctx.lineTo(x, y);
            }
            ctx.stroke();
            animationFrameId = requestAnimationFrame(render);
        };
        render();
        return () => cancelAnimationFrame(animationFrameId);
    }, [scanning]);
    // 3. 72 小時絕對窗口計時器(綁定本地儲存防重整)
    useEffect(() => {
        if (scanning || !report) return;
        const storageKey = `gubon_deadline_${id}`;
        let deadline = localStorage.getItem(storageKey);
        if (!deadline) {
            deadline = Date.now() + 72 * 60 * 60 * 1000; 
            localStorage.setItem(storageKey, deadline.toString());
        } else {
            deadline = parseInt(deadline, 10);
        }
        const timer = setInterval(() => {
            const now = Date.now();
            const distance = deadline - now;
            if (distance < 0) {
                clearInterval(timer);
                setTimeLeft("決策窗口已永久關閉");
                return;
            }
            const hours = Math.floor(distance / (1000 * 60 * 60));
            const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
            const seconds = Math.floor((distance % (1000 * 60)) / 1000);
            setTimeLeft(`${hours}小時 ${minutes}分 ${seconds}秒`);
        }, 1000);
        return () => clearInterval(timer);
    }, [scanning, report, id]);
    if (scanning) {
        return (
            <div className="max-w-md mx-auto p-6 bg-black text-white text-center font-sans">
                <div className="animate-pulse text-red-500 mb-4 font-bold">正在掃描宇宙維度...請勿離開</div>
                <canvas ref={canvasRef} className="w-full mb-4 bg-gray-900 rounded" />
                <div className="text-sm text-gray-400">矩陣數據解析進度:{scanProgress}%</div>
            </div>
        );
    }
    return (
        <div className="max-w-md mx-auto p-6 bg-black text-white font-sans selection:bg-red-600">
            <h1 className="text-2xl font-bold border-b border-red-600 pb-2 tracking-wide">GUBON 決策摘要</h1>
            <p className="mt-4 text-gray-300 leading-relaxed">{report?.summary}</p>
            <div className="mt-4 text-sm text-yellow-500 font-mono">命運決策窗口倒數:{timeLeft}</div>
            {!report?.isPaid && (
                <div className="mt-8 p-6 border-2 border-red-600 bg-red-950 bg-opacity-30 rounded shadow-lg shadow-red-900/50">
                    <h2 className="text-xl font-bold mb-2 flex items-center gap-2 text-red-500">⚠️ 核心指令已鎖定</h2>
                    <p className="text-sm text-gray-300 mb-4">如果不立即解鎖此決策,您在接下來 72 小時內將面臨不可挽回的資源與運勢損失。</p>
                    <button 
                        onClick={async () => {
                            const res = await fetch(`${process.env.REACT_APP_BACKEND_URL}/v1/payment/create-checkout`, { 
                                method: 'POST', 
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({ reportId: id }) 
                            });
                            const { url } = await res.json();
                            window.location.href = url;
                        }}
                        className="w-full py-4 bg-red-600 hover:bg-red-700 active:scale-95 transition-all text-white font-bold uppercase tracking-widest rounded text-center"
                    >
                        立即解鎖決策 (NT$ 880)
                    </button>
                </div>
            )}
            {report?.isPaid && (
                <div className="mt-8 p-6 border-2 border-green-500 bg-green-950 bg-opacity-20 rounded animate-fade-in">
                    <h2 className="text-green-400 font-bold mb-2 tracking-wider">執行唯一決策指令 [核心矩陣點數: {report?.fullContent?.matrixScore}]:</h2>
                    <p className="text-xl font-semibold text-white leading-relaxed bg-black p-4 border border-gray-800 rounded">{report?.fullContent?.decision}</p>
                </div>
            )}
        </div>
    );
}
```
## 6. 自動化變現閉環:LINE 遞進式催單排程
**職責**:利用排程(可透過定時器觸發),精準掃描 3 天前建立、留了 LINE 但未付款的漏斗用戶,施加 72 小時損失感壓力。
```javascript
// src/api/services/lineScheduler.js
import { PrismaClient } from '@prisma/client';
import axios from 'axios';
const prisma = new PrismaClient();
export async function triggerLineFollowUp() {
    const targetDate = new Date();
    targetDate.setDate(targetDate.getDate() - 3); // 篩選出滿 3 天(72小時臨界點)的資料
    const pendingReports = await prisma.report.findMany({
        where: { 
            createdAt: { lte: targetDate }, 
            isPaid: false,
            status: 'completed'
        },
        include: { user: true }
    });
    console.log(`[LINE Scheduler] Found ${pendingReports.length} pending critical reports to push.`);
    for (const report of pendingReports) {
        if (report.user.lineId) {
            try {
                await axios.post('https://api.line.me/v2/bot/message/push', {
                    to: report.user.lineId,
                    messages: [{
                        type: 'text',
                        text: `⚠️【最後警告】您於三日前請求的 GUBON 絕對決策指令即將永久銷毀。當前觀測到您的決策窗口正在關閉,請立即回訪處理,避免 72 小時內核心資源遭受不可逆損失:${process.env.FRONTEND_URL}/report/${report.id}`
                    }]
                }, {
                    headers: { 'Authorization': `Bearer ${process.env.LINE_CHANNEL_ACCESS_TOKEN}` }
                });
                console.log(`[LINE Push Success] Dispatched to user ${report.user.id}`);
            } catch (err) {
                console.error(`[LINE Push Failed] Report ID ${report.id}:`, err.message);
            }
        }
    }
}
version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    container_name: gubon-postgres
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: gubon
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always
  redis:
    image: redis:7-alpine
    container_name: gubon-redis
    ports:
      - "6379:6379"
    restart: always
volumes:
  pgdata:
PORT=3000
DATABASE_URL="postgresql://user:password@localhost:5432/gubon?schema=public"
REDIS_URL="redis://localhost:6379"
# 變現與 AI 核心密鑰
STRIPE_SECRET_KEY="sk_live_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
STRIPE_PRICE_ID="price_..."
OPENAI_API_KEY="sk-proj-..."
LINE_CHANNEL_ACCESS_TOKEN="..."
# 前端跳轉域名
FRONTEND_URL="https://gubon-os.com"
# React 限制必須以 REACT_APP_ 開頭才能在瀏覽器端載入
REACT_APP_BACKEND_URL="https://api.gubon-os.com"// 可以直接在 src/api/server.js 底部加入(例如每小時檢查一次)
import { triggerLineFollowUp } from '../services/lineScheduler.js';
setInterval(() => {
    console.log('[Cron Job] Executing 72H LINE Follow-Up scan...');
    triggerLineFollowUp();
}, 60 * 60 * 1000); // 每 60 分鐘執行一次
# 1. 啟動 PostgreSQL 與 Redis 容器
docker-compose up -d
# 2. 同步資料庫模型並生成 Prisma Client
npx prisma db push
# 3. 啟動後端主伺服器
node src/api/server.js
# 4. 啟動 AI 處理器 Worker 進程 (另開 Terminal)
node src/api/worker/reportWorker.js
// src/api/server.js 底部
import { triggerLineFollowUp } from '../services/lineScheduler.js';
setInterval(() => {
    console.log('[Cron Job] Executing 72H LINE Follow-Up scan...');
    triggerLineFollowUp();
}, 60 * 60 * 1000); // 每 60 分鐘自動掃描未付費的流失用戶
// ...(前面原本的 Express, Stripe, Webhook 程式碼保持不變)

// Socket.io 房間綁定
io.on('connection', (socket) => {
    socket.on('join', (reportId) => { socket.join(reportId); });
});

// ==================== 修正後的排程催單機制 ====================
// 注意:確保 lineScheduler.js 檔案存在於 ../services/ 之中
import { triggerLineFollowUp } from '../services/lineScheduler.js';

// 每 60 分鐘自動掃描並催單 72 小時前未付費的流失用戶
setInterval(async () => {
    console.log('[Cron Job] Executing 72H LINE Follow-Up scan...');
    try {
        await triggerLineFollowUp();
    } catch (err) {
        console.error('[Cron Job Error]:', err.message);
    }
}, 60 * 60 * 1000); 
// ==============================================================

const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => console.log(`[API Server] Running on port ${PORT}`));
// src/services/lineScheduler.js
import { PrismaClient } from '@prisma/client';
import axios from 'axios';

const prisma = new PrismaClient();

export async function triggerLineFollowUp() {
    const now = new Date();
    // 建立 72 小時前的時間窗口區間(例如 72 小時前 到 73 小時前)
    const lowerBound = new Date(now.getTime() - 73 * 60 * 60 * 1000);
    const upperBound = new Date(now.getTime() - 72 * 60 * 60 * 1000);

    const pendingReports = await prisma.report.findMany({
        where: { 
            createdAt: {
                gte: lowerBound,
                lte: upperBound
            }, 
            isPaid: false,
            status: 'completed'
        },
        include: { user: true }
    });

    console.log(`[LINE Scheduler] Found ${pendingReports.length} pending critical reports 

About

HTML Standard

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • HTML 100.0%