From 143a852156ca82893f6ae7d275839538fb1040b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Tue, 24 Feb 2026 14:57:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A0=E7=94=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20=E5=BF=BD=E7=95=A5=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + next-project/.env | 3 - next-project/.env.port.example | 32 - next-project/.github-upload-rules.md | 389 - next-project/.gitignore | 13 - next-project/DEPLOYMENT.md | 202 - next-project/PRISMA_迁移完成.md | 146 - next-project/README.md | 16 - next-project/SYNC_LOG.md | 1 - .../.cursorrules | 68 - .../1_核心设计_通用协议/API接口定义.md | 382 - .../1_核心设计_通用协议/业务逻辑与模型.md | 396 - .../1_核心设计_通用协议/安全与合规.md | 383 - .../1_核心设计_通用协议/标准配置模板.yaml | 133 - .../2_智能对接_AI指令/Cursor规则.md | 266 - .../2_智能对接_AI指令/通用集成指令.md | 234 - .../3_逻辑参考_通用实现/前端收银台Demo.html | 748 - .../后端源码/python/alipay_gateway.py | 294 - .../后端源码/python/payment_factory.py | 339 - .../后端源码/python/wechat_gateway.py | 317 - .../4_卡若配置/env.example.txt | 101 - .../Universal_Payment_Module copy/README.md | 142 - next-project/api/soul导师顾问接口.md | 413 - next-project/api/soul资源对接接口.md | 413 - next-project/app/admin/chapters/page.tsx | 293 - next-project/app/admin/content/loading.tsx | 3 - next-project/app/admin/content/page.tsx | 1123 -- .../app/admin/distribution/loading.tsx | 35 - next-project/app/admin/distribution/page.tsx | 1015 -- next-project/app/admin/layout.tsx | 123 - next-project/app/admin/loading.tsx | 3 - next-project/app/admin/login/loading.tsx | 3 - next-project/app/admin/login/page.tsx | 111 - next-project/app/admin/match/page.tsx | 517 - next-project/app/admin/orders/page.tsx | 275 - next-project/app/admin/page.tsx | 291 - next-project/app/admin/payment/loading.tsx | 3 - next-project/app/admin/payment/page.tsx | 375 - next-project/app/admin/qrcodes/loading.tsx | 3 - next-project/app/admin/qrcodes/page.tsx | 225 - .../app/admin/referral-settings/page.tsx | 272 - next-project/app/admin/settings/loading.tsx | 3 - next-project/app/admin/settings/page.tsx | 645 - next-project/app/admin/site/loading.tsx | 14 - next-project/app/admin/site/page.tsx | 384 - next-project/app/admin/users/loading.tsx | 3 - next-project/app/admin/users/page.tsx | 706 - .../app/admin/withdrawals/loading.tsx | 3 - next-project/app/admin/withdrawals/page.tsx | 354 - next-project/app/api/admin/chapters/route.ts | 337 - next-project/app/api/admin/content/route.ts | 188 - .../api/admin/distribution/overview/route.ts | 268 - next-project/app/api/admin/logout/route.ts | 9 - next-project/app/api/admin/payment/route.ts | 191 - next-project/app/api/admin/referral/route.ts | 258 - next-project/app/api/admin/route.ts | 90 - .../app/api/admin/withdrawals/route.ts | 222 - next-project/app/api/auth/login/route.ts | 72 - .../app/api/auth/reset-password/route.ts | 54 - .../app/api/book/all-chapters/route.ts | 161 - .../app/api/book/chapter/[id]/route.ts | 63 - next-project/app/api/book/chapters/route.ts | 218 - next-project/app/api/book/hot/route.ts | 86 - .../app/api/book/latest-chapters/route.ts | 109 - next-project/app/api/book/search/route.ts | 150 - next-project/app/api/book/stats/route.ts | 26 - next-project/app/api/book/sync/route.ts | 72 - next-project/app/api/ckb/join/route.ts | 137 - next-project/app/api/ckb/match/route.ts | 131 - next-project/app/api/ckb/sync/route.ts | 525 - next-project/app/api/config/route.ts | 70 - next-project/app/api/content/route.ts | 36 - .../app/api/cron/sync-orders/route.ts | 256 - .../app/api/cron/unbind-expired/route.ts | 152 - next-project/app/api/db/book/route.ts | 475 - next-project/app/api/db/chapters/route.ts | 272 - next-project/app/api/db/config/route.ts | 137 - next-project/app/api/db/distribution/route.ts | 73 - next-project/app/api/db/init/route.ts | 197 - next-project/app/api/db/migrate/route.ts | 260 - .../app/api/db/users/referrals/route.ts | 122 - next-project/app/api/db/users/route.ts | 274 - .../auto-withdraw-config/route.ts | 108 - .../app/api/distribution/messages/route.ts | 53 - next-project/app/api/distribution/route.ts | 885 -- .../app/api/documentation/generate/route.ts | 55 - next-project/app/api/match/config/route.ts | 74 - next-project/app/api/match/users/route.ts | 84 - next-project/app/api/menu/route.ts | 12 - .../app/api/miniprogram/login/route.ts | 179 - .../app/api/miniprogram/pay/notify/route.ts | 486 - next-project/app/api/miniprogram/pay/route.ts | 437 - .../app/api/miniprogram/phone/route.ts | 86 - .../app/api/miniprogram/qrcode/route.ts | 114 - next-project/app/api/orders/route.ts | 96 - .../app/api/payment/alipay/notify/route.ts | 155 - .../app/api/payment/callback/route.ts | 51 - .../app/api/payment/create-order/route.ts | 174 - next-project/app/api/payment/methods/route.ts | 37 - next-project/app/api/payment/query/route.ts | 143 - .../app/api/payment/status/[orderSn]/route.ts | 79 - next-project/app/api/payment/verify/route.ts | 51 - .../app/api/payment/wechat/notify/route.ts | 156 - .../payment/wechat/transfer/notify/route.ts | 102 - next-project/app/api/referral/bind/route.ts | 206 - next-project/app/api/referral/data/route.ts | 324 - next-project/app/api/referral/visit/route.ts | 100 - next-project/app/api/search/route.ts | 273 - next-project/app/api/sync/route.ts | 230 - next-project/app/api/upload/route.ts | 134 - .../app/api/user/addresses/[id]/route.ts | 112 - next-project/app/api/user/addresses/route.ts | 68 - .../app/api/user/check-purchased/route.ts | 108 - next-project/app/api/user/profile/route.ts | 195 - .../app/api/user/purchase-status/route.ts | 72 - .../app/api/user/reading-progress/route.ts | 140 - next-project/app/api/user/track/route.ts | 221 - next-project/app/api/user/update/route.ts | 51 - next-project/app/api/wechat/login/route.ts | 157 - .../app/api/withdraw/pending-confirm/route.ts | 53 - .../app/api/withdraw/records/route.ts | 45 - next-project/app/api/withdraw/route.ts | 181 - next-project/app/error.tsx | 52 - next-project/app/globals.css | 519 - next-project/app/layout.tsx | 48 - next-project/app/loading.tsx | 18 - next-project/app/page.tsx | 19 - next-project/app/view/about/page.tsx | 123 - next-project/app/view/chapters/page.tsx | 214 - next-project/app/view/docs/page.tsx | 112 - .../view/documentation/capture/loading.tsx | 3 - .../app/view/documentation/capture/page.tsx | 78 - next-project/app/view/documentation/page.tsx | 306 - next-project/app/view/login/forgot/page.tsx | 136 - next-project/app/view/login/page.tsx | 230 - next-project/app/view/match/page.tsx | 849 -- .../app/view/my/addresses/[id]/page.tsx | 161 - .../app/view/my/addresses/new/page.tsx | 163 - next-project/app/view/my/addresses/page.tsx | 141 - next-project/app/view/my/page.tsx | 551 - next-project/app/view/my/purchases/page.tsx | 109 - next-project/app/view/my/referral/page.tsx | 569 - next-project/app/view/my/settings/page.tsx | 276 - next-project/app/view/page.tsx | 221 - next-project/app/view/read/[id]/page.tsx | 92 - next-project/app/view/temp_page.tsx | 144 - next-project/components.json | 21 - next-project/components/README.md | 22 - next-project/components/admin/ui/button.tsx | 60 - next-project/components/auth-modal.tsx | 225 - next-project/components/book-cover.tsx | 124 - next-project/components/book-intro.tsx | 55 - next-project/components/bottom-nav.tsx | 104 - .../components/buy-full-book-button.tsx | 56 - next-project/components/chapter-content.tsx | 433 - next-project/components/chapters-list.tsx | 135 - next-project/components/config-loader.tsx | 14 - next-project/components/footer.tsx | 41 - next-project/components/layout-wrapper.tsx | 43 - next-project/components/layout/bottom-nav.tsx | 47 - .../components/modules/auth/auth-modal.tsx | 244 - .../distribution/auto-withdraw-modal.tsx | 291 - .../distribution/realtime-notification.tsx | 307 - .../modules/marketing/qr-code-modal.tsx | 123 - .../modules/payment/payment-modal.tsx | 343 - .../modules/referral/poster-modal.tsx | 126 - .../modules/referral/referral-share.tsx | 48 - .../modules/referral/withdrawal-modal.tsx | 247 - .../modules/user/user-detail-modal.tsx | 588 - .../components/party-group-section.tsx | 35 - next-project/components/payment-modal.tsx | 685 - next-project/components/purchase-section.tsx | 149 - next-project/components/qr-code-modal.tsx | 78 - next-project/components/search-modal.tsx | 212 - next-project/components/table-of-contents.tsx | 90 - next-project/components/theme-provider.tsx | 11 - next-project/components/ui/badge.tsx | 46 - next-project/components/ui/button.tsx | 60 - next-project/components/ui/card.tsx | 76 - next-project/components/ui/dialog.tsx | 143 - next-project/components/ui/input.tsx | 21 - next-project/components/ui/label.tsx | 22 - next-project/components/ui/select.tsx | 160 - next-project/components/ui/slider.tsx | 63 - next-project/components/ui/switch.tsx | 28 - next-project/components/ui/table.tsx | 117 - next-project/components/ui/tabs.tsx | 54 - next-project/components/ui/textarea.tsx | 20 - next-project/components/user-menu.tsx | 107 - .../components/view/config/config-loader.tsx | 14 - .../components/view/layout/bottom-nav.tsx | 94 - .../components/view/layout/layout-wrapper.tsx | 46 - .../view/layout/referral-capture.tsx | 38 - next-project/components/view/ui/button.tsx | 60 - next-project/deploy-production.sh | 52 - next-project/deploy_to_nas.sh | 325 - next-project/devlop.py | 775 - next-project/ecosystem.config.cjs | 19 - next-project/lib/admin-auth.ts | 92 - next-project/lib/book-data.ts | 725 - next-project/lib/book-file-system.ts | 169 - next-project/lib/db.ts | 376 - next-project/lib/documentation/catalog.ts | 151 - next-project/lib/documentation/docx.ts | 272 - next-project/lib/documentation/screenshot.ts | 98 - next-project/lib/markdown.ts | 39 - .../lib/modules/distribution/auto-payment.ts | 561 - .../lib/modules/distribution/index.ts | 102 - .../lib/modules/distribution/service.ts | 910 -- .../lib/modules/distribution/types.ts | 254 - .../lib/modules/distribution/websocket.ts | 314 - next-project/lib/modules/marketing/types.ts | 37 - next-project/lib/modules/payment/types.ts | 26 - next-project/lib/modules/referral/types.ts | 32 - next-project/lib/password.ts | 56 - next-project/lib/payment-service.ts | 117 - next-project/lib/payment-store.ts | 115 - next-project/lib/payment/alipay.ts | 614 - next-project/lib/payment/config.ts | 126 - next-project/lib/payment/factory.ts | 246 - next-project/lib/payment/index.ts | 32 - next-project/lib/payment/types.ts | 289 - next-project/lib/payment/wechat.ts | 615 - next-project/lib/prisma-helpers.ts | 140 - next-project/lib/prisma.ts | 36 - next-project/lib/store.ts | 754 - next-project/lib/utils.ts | 6 - next-project/lib/wechat-transfer.ts | 382 - next-project/middleware.ts | 46 - next-project/next-env.d.ts | 6 - next-project/next.config.mjs | 29 - next-project/package.json | 62 - next-project/pnpm-lock.yaml | 11825 ---------------- next-project/postcss.config.mjs | 8 - next-project/prisma.config.ts | 14 - .../migrations/add_withdrawals_wechat_id.sql | 6 - next-project/prisma/schema.prisma | 310 - next-project/public/apple-icon.png | Bin 2626 -> 0 bytes next-project/public/assets/.gitkeep | 3 - .../assets/avatars/1770295261234_oa16yh.jpeg | Bin 3317 -> 0 bytes next-project/public/book-chapters.json | 576 - next-project/public/icon-dark-32x32.png | Bin 585 -> 0 bytes next-project/public/icon-light-32x32.png | Bin 566 -> 0 bytes next-project/public/icon.svg | 26 - next-project/public/images/alipay.png | Bin 378643 -> 0 bytes .../b05c111ec1aefab166379b5d146b3efc.jpg | Bin 99156 -> 0 bytes next-project/public/images/image.png | Bin 378643 -> 0 bytes next-project/public/images/party-group-qr.png | Bin 378643 -> 0 bytes next-project/public/images/wechat-pay.png | Bin 378643 -> 0 bytes next-project/public/placeholder-logo.png | Bin 568 -> 0 bytes next-project/public/placeholder-logo.svg | 1 - next-project/public/placeholder-user.jpg | Bin 1635 -> 0 bytes next-project/public/placeholder.jpg | Bin 1064 -> 0 bytes next-project/public/placeholder.svg | 1 - next-project/quick-push.sh | 49 - next-project/quick_deploy.sh | 50 - next-project/redeploy.sh | 128 - .../scripts/__pycache__/deploy.cpython-37.pyc | Bin 13286 -> 0 bytes .../deploy_baota_pure_api.cpython-311.pyc | Bin 8116 -> 0 bytes .../__pycache__/deploy_soul.cpython-311.pyc | Bin 46014 -> 0 bytes .../__pycache__/deploy_soul.cpython-38.pyc | Bin 22894 -> 0 bytes .../scripts/auto-unbind-expired-simple.js | 168 - next-project/scripts/auto-unbind-expired.js | 170 - .../scripts/check-withdrawals-data.sql | 47 - next-project/scripts/check_deployment.py | 62 - next-project/scripts/clean-standalone.js | 68 - next-project/scripts/devlopTest.py | 729 - next-project/scripts/fix-binding-styles.txt | 115 - .../scripts/fix-earnings-detail-styles.py | 210 - .../scripts/migrate-chapters-to-db.js | 224 - next-project/scripts/migrate-to-prisma.js | 137 - .../scripts/migrate_binding_fields.py | 162 - next-project/scripts/migrate_db_simple.py | 158 - .../scripts/migration-add-binding-fields.sql | 25 - .../scripts/optimize-referral-indexes.sql | 194 - next-project/scripts/prepare-standalone.js | 103 - .../scripts/remove-referred-by-field-auto.py | 227 - .../scripts/remove-referred-by-field.py | 231 - .../scripts/remove-referred-by-field.sql | 108 - next-project/scripts/start-standalone.js | 133 - next-project/scripts/sync-book-content.js | 115 - next-project/scripts/test-referral-config.js | 145 - next-project/scripts/test-referral-flow.js | 338 - next-project/scripts/test-withdrawals-api.js | 34 - .../scripts/verify-withdrawal-data.sql | 203 - .../scripts/write-standalone-warning.js | 24 - next-project/setup-github-token.sh | 129 - next-project/styles/globals.css | 125 - next-project/tsconfig.json | 41 - next-project/tsconfig.tsbuildinfo | 1 - .../.vite/deps/@radix-ui_react-dialog.js | 16 +- .../.vite/deps/@radix-ui_react-label.js | 10 +- .../.vite/deps/@radix-ui_react-select.js | 32 +- .../.vite/deps/@radix-ui_react-select.js.map | 4 +- .../.vite/deps/@radix-ui_react-slider.js | 20 +- .../.vite/deps/@radix-ui_react-slot.js | 8 +- .../.vite/deps/@radix-ui_react-switch.js | 12 +- .../.vite/deps/@radix-ui_react-tabs.js | 20 +- .../node_modules/.vite/deps/_metadata.json | 93 +- .../.vite/deps/class-variance-authority.js | 18 +- .../deps/class-variance-authority.js.map | 6 +- soul-admin/node_modules/.vite/deps/clsx.js | 21 +- .../node_modules/.vite/deps/clsx.js.map | 6 +- .../node_modules/.vite/deps/lucide-react.js | 2 +- .../.vite/deps/lucide-react.js.map | 2 +- .../node_modules/.vite/deps/react-dom.js | 4 +- .../.vite/deps/react-dom_client.js | 4 +- .../.vite/deps/react-dom_client.js.map | 2 +- .../.vite/deps/react-router-dom.js | 10 +- .../.vite/deps/react-router-dom.js.map | 2 +- soul-admin/node_modules/.vite/deps/react.js | 2 +- .../.vite/deps/react_jsx-dev-runtime.js | 2 +- .../.vite/deps/react_jsx-dev-runtime.js.map | 2 +- .../.vite/deps/react_jsx-runtime.js | 4 +- .../.vite/deps/tailwind-merge.js.map | 2 +- 315 files changed, 142 insertions(+), 62037 deletions(-) delete mode 100644 next-project/.env delete mode 100644 next-project/.env.port.example delete mode 100644 next-project/.github-upload-rules.md delete mode 100644 next-project/.gitignore delete mode 100644 next-project/DEPLOYMENT.md delete mode 100644 next-project/PRISMA_迁移完成.md delete mode 100644 next-project/README.md delete mode 100644 next-project/SYNC_LOG.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/.cursorrules delete mode 100644 next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml delete mode 100644 next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md delete mode 100644 next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html delete mode 100644 next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py delete mode 100644 next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py delete mode 100644 next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py delete mode 100644 next-project/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt delete mode 100644 next-project/addons/Universal_Payment_Module copy/README.md delete mode 100644 next-project/api/soul导师顾问接口.md delete mode 100644 next-project/api/soul资源对接接口.md delete mode 100644 next-project/app/admin/chapters/page.tsx delete mode 100644 next-project/app/admin/content/loading.tsx delete mode 100644 next-project/app/admin/content/page.tsx delete mode 100644 next-project/app/admin/distribution/loading.tsx delete mode 100644 next-project/app/admin/distribution/page.tsx delete mode 100644 next-project/app/admin/layout.tsx delete mode 100644 next-project/app/admin/loading.tsx delete mode 100644 next-project/app/admin/login/loading.tsx delete mode 100644 next-project/app/admin/login/page.tsx delete mode 100644 next-project/app/admin/match/page.tsx delete mode 100644 next-project/app/admin/orders/page.tsx delete mode 100644 next-project/app/admin/page.tsx delete mode 100644 next-project/app/admin/payment/loading.tsx delete mode 100644 next-project/app/admin/payment/page.tsx delete mode 100644 next-project/app/admin/qrcodes/loading.tsx delete mode 100644 next-project/app/admin/qrcodes/page.tsx delete mode 100644 next-project/app/admin/referral-settings/page.tsx delete mode 100644 next-project/app/admin/settings/loading.tsx delete mode 100644 next-project/app/admin/settings/page.tsx delete mode 100644 next-project/app/admin/site/loading.tsx delete mode 100644 next-project/app/admin/site/page.tsx delete mode 100644 next-project/app/admin/users/loading.tsx delete mode 100644 next-project/app/admin/users/page.tsx delete mode 100644 next-project/app/admin/withdrawals/loading.tsx delete mode 100644 next-project/app/admin/withdrawals/page.tsx delete mode 100644 next-project/app/api/admin/chapters/route.ts delete mode 100644 next-project/app/api/admin/content/route.ts delete mode 100644 next-project/app/api/admin/distribution/overview/route.ts delete mode 100644 next-project/app/api/admin/logout/route.ts delete mode 100644 next-project/app/api/admin/payment/route.ts delete mode 100644 next-project/app/api/admin/referral/route.ts delete mode 100644 next-project/app/api/admin/route.ts delete mode 100644 next-project/app/api/admin/withdrawals/route.ts delete mode 100644 next-project/app/api/auth/login/route.ts delete mode 100644 next-project/app/api/auth/reset-password/route.ts delete mode 100644 next-project/app/api/book/all-chapters/route.ts delete mode 100644 next-project/app/api/book/chapter/[id]/route.ts delete mode 100644 next-project/app/api/book/chapters/route.ts delete mode 100644 next-project/app/api/book/hot/route.ts delete mode 100644 next-project/app/api/book/latest-chapters/route.ts delete mode 100644 next-project/app/api/book/search/route.ts delete mode 100644 next-project/app/api/book/stats/route.ts delete mode 100644 next-project/app/api/book/sync/route.ts delete mode 100644 next-project/app/api/ckb/join/route.ts delete mode 100644 next-project/app/api/ckb/match/route.ts delete mode 100644 next-project/app/api/ckb/sync/route.ts delete mode 100644 next-project/app/api/config/route.ts delete mode 100644 next-project/app/api/content/route.ts delete mode 100644 next-project/app/api/cron/sync-orders/route.ts delete mode 100644 next-project/app/api/cron/unbind-expired/route.ts delete mode 100644 next-project/app/api/db/book/route.ts delete mode 100644 next-project/app/api/db/chapters/route.ts delete mode 100644 next-project/app/api/db/config/route.ts delete mode 100644 next-project/app/api/db/distribution/route.ts delete mode 100644 next-project/app/api/db/init/route.ts delete mode 100644 next-project/app/api/db/migrate/route.ts delete mode 100644 next-project/app/api/db/users/referrals/route.ts delete mode 100644 next-project/app/api/db/users/route.ts delete mode 100644 next-project/app/api/distribution/auto-withdraw-config/route.ts delete mode 100644 next-project/app/api/distribution/messages/route.ts delete mode 100644 next-project/app/api/distribution/route.ts delete mode 100644 next-project/app/api/documentation/generate/route.ts delete mode 100644 next-project/app/api/match/config/route.ts delete mode 100644 next-project/app/api/match/users/route.ts delete mode 100644 next-project/app/api/menu/route.ts delete mode 100644 next-project/app/api/miniprogram/login/route.ts delete mode 100644 next-project/app/api/miniprogram/pay/notify/route.ts delete mode 100644 next-project/app/api/miniprogram/pay/route.ts delete mode 100644 next-project/app/api/miniprogram/phone/route.ts delete mode 100644 next-project/app/api/miniprogram/qrcode/route.ts delete mode 100644 next-project/app/api/orders/route.ts delete mode 100644 next-project/app/api/payment/alipay/notify/route.ts delete mode 100644 next-project/app/api/payment/callback/route.ts delete mode 100644 next-project/app/api/payment/create-order/route.ts delete mode 100644 next-project/app/api/payment/methods/route.ts delete mode 100644 next-project/app/api/payment/query/route.ts delete mode 100644 next-project/app/api/payment/status/[orderSn]/route.ts delete mode 100644 next-project/app/api/payment/verify/route.ts delete mode 100644 next-project/app/api/payment/wechat/notify/route.ts delete mode 100644 next-project/app/api/payment/wechat/transfer/notify/route.ts delete mode 100644 next-project/app/api/referral/bind/route.ts delete mode 100644 next-project/app/api/referral/data/route.ts delete mode 100644 next-project/app/api/referral/visit/route.ts delete mode 100644 next-project/app/api/search/route.ts delete mode 100644 next-project/app/api/sync/route.ts delete mode 100644 next-project/app/api/upload/route.ts delete mode 100644 next-project/app/api/user/addresses/[id]/route.ts delete mode 100644 next-project/app/api/user/addresses/route.ts delete mode 100644 next-project/app/api/user/check-purchased/route.ts delete mode 100644 next-project/app/api/user/profile/route.ts delete mode 100644 next-project/app/api/user/purchase-status/route.ts delete mode 100644 next-project/app/api/user/reading-progress/route.ts delete mode 100644 next-project/app/api/user/track/route.ts delete mode 100644 next-project/app/api/user/update/route.ts delete mode 100644 next-project/app/api/wechat/login/route.ts delete mode 100644 next-project/app/api/withdraw/pending-confirm/route.ts delete mode 100644 next-project/app/api/withdraw/records/route.ts delete mode 100644 next-project/app/api/withdraw/route.ts delete mode 100644 next-project/app/error.tsx delete mode 100644 next-project/app/globals.css delete mode 100644 next-project/app/layout.tsx delete mode 100644 next-project/app/loading.tsx delete mode 100644 next-project/app/page.tsx delete mode 100644 next-project/app/view/about/page.tsx delete mode 100644 next-project/app/view/chapters/page.tsx delete mode 100644 next-project/app/view/docs/page.tsx delete mode 100644 next-project/app/view/documentation/capture/loading.tsx delete mode 100644 next-project/app/view/documentation/capture/page.tsx delete mode 100644 next-project/app/view/documentation/page.tsx delete mode 100644 next-project/app/view/login/forgot/page.tsx delete mode 100644 next-project/app/view/login/page.tsx delete mode 100644 next-project/app/view/match/page.tsx delete mode 100644 next-project/app/view/my/addresses/[id]/page.tsx delete mode 100644 next-project/app/view/my/addresses/new/page.tsx delete mode 100644 next-project/app/view/my/addresses/page.tsx delete mode 100644 next-project/app/view/my/page.tsx delete mode 100644 next-project/app/view/my/purchases/page.tsx delete mode 100644 next-project/app/view/my/referral/page.tsx delete mode 100644 next-project/app/view/my/settings/page.tsx delete mode 100644 next-project/app/view/page.tsx delete mode 100644 next-project/app/view/read/[id]/page.tsx delete mode 100644 next-project/app/view/temp_page.tsx delete mode 100644 next-project/components.json delete mode 100644 next-project/components/README.md delete mode 100644 next-project/components/admin/ui/button.tsx delete mode 100644 next-project/components/auth-modal.tsx delete mode 100644 next-project/components/book-cover.tsx delete mode 100644 next-project/components/book-intro.tsx delete mode 100644 next-project/components/bottom-nav.tsx delete mode 100644 next-project/components/buy-full-book-button.tsx delete mode 100644 next-project/components/chapter-content.tsx delete mode 100644 next-project/components/chapters-list.tsx delete mode 100644 next-project/components/config-loader.tsx delete mode 100644 next-project/components/footer.tsx delete mode 100644 next-project/components/layout-wrapper.tsx delete mode 100644 next-project/components/layout/bottom-nav.tsx delete mode 100644 next-project/components/modules/auth/auth-modal.tsx delete mode 100644 next-project/components/modules/distribution/auto-withdraw-modal.tsx delete mode 100644 next-project/components/modules/distribution/realtime-notification.tsx delete mode 100644 next-project/components/modules/marketing/qr-code-modal.tsx delete mode 100644 next-project/components/modules/payment/payment-modal.tsx delete mode 100644 next-project/components/modules/referral/poster-modal.tsx delete mode 100644 next-project/components/modules/referral/referral-share.tsx delete mode 100644 next-project/components/modules/referral/withdrawal-modal.tsx delete mode 100644 next-project/components/modules/user/user-detail-modal.tsx delete mode 100644 next-project/components/party-group-section.tsx delete mode 100644 next-project/components/payment-modal.tsx delete mode 100644 next-project/components/purchase-section.tsx delete mode 100644 next-project/components/qr-code-modal.tsx delete mode 100644 next-project/components/search-modal.tsx delete mode 100644 next-project/components/table-of-contents.tsx delete mode 100644 next-project/components/theme-provider.tsx delete mode 100644 next-project/components/ui/badge.tsx delete mode 100644 next-project/components/ui/button.tsx delete mode 100644 next-project/components/ui/card.tsx delete mode 100644 next-project/components/ui/dialog.tsx delete mode 100644 next-project/components/ui/input.tsx delete mode 100644 next-project/components/ui/label.tsx delete mode 100644 next-project/components/ui/select.tsx delete mode 100644 next-project/components/ui/slider.tsx delete mode 100644 next-project/components/ui/switch.tsx delete mode 100644 next-project/components/ui/table.tsx delete mode 100644 next-project/components/ui/tabs.tsx delete mode 100644 next-project/components/ui/textarea.tsx delete mode 100644 next-project/components/user-menu.tsx delete mode 100644 next-project/components/view/config/config-loader.tsx delete mode 100644 next-project/components/view/layout/bottom-nav.tsx delete mode 100644 next-project/components/view/layout/layout-wrapper.tsx delete mode 100644 next-project/components/view/layout/referral-capture.tsx delete mode 100644 next-project/components/view/ui/button.tsx delete mode 100644 next-project/deploy-production.sh delete mode 100644 next-project/deploy_to_nas.sh delete mode 100644 next-project/devlop.py delete mode 100644 next-project/ecosystem.config.cjs delete mode 100644 next-project/lib/admin-auth.ts delete mode 100644 next-project/lib/book-data.ts delete mode 100644 next-project/lib/book-file-system.ts delete mode 100644 next-project/lib/db.ts delete mode 100644 next-project/lib/documentation/catalog.ts delete mode 100644 next-project/lib/documentation/docx.ts delete mode 100644 next-project/lib/documentation/screenshot.ts delete mode 100644 next-project/lib/markdown.ts delete mode 100644 next-project/lib/modules/distribution/auto-payment.ts delete mode 100644 next-project/lib/modules/distribution/index.ts delete mode 100644 next-project/lib/modules/distribution/service.ts delete mode 100644 next-project/lib/modules/distribution/types.ts delete mode 100644 next-project/lib/modules/distribution/websocket.ts delete mode 100644 next-project/lib/modules/marketing/types.ts delete mode 100644 next-project/lib/modules/payment/types.ts delete mode 100644 next-project/lib/modules/referral/types.ts delete mode 100644 next-project/lib/password.ts delete mode 100644 next-project/lib/payment-service.ts delete mode 100644 next-project/lib/payment-store.ts delete mode 100644 next-project/lib/payment/alipay.ts delete mode 100644 next-project/lib/payment/config.ts delete mode 100644 next-project/lib/payment/factory.ts delete mode 100644 next-project/lib/payment/index.ts delete mode 100644 next-project/lib/payment/types.ts delete mode 100644 next-project/lib/payment/wechat.ts delete mode 100644 next-project/lib/prisma-helpers.ts delete mode 100644 next-project/lib/prisma.ts delete mode 100644 next-project/lib/store.ts delete mode 100644 next-project/lib/utils.ts delete mode 100644 next-project/lib/wechat-transfer.ts delete mode 100644 next-project/middleware.ts delete mode 100644 next-project/next-env.d.ts delete mode 100644 next-project/next.config.mjs delete mode 100644 next-project/package.json delete mode 100644 next-project/pnpm-lock.yaml delete mode 100644 next-project/postcss.config.mjs delete mode 100644 next-project/prisma.config.ts delete mode 100644 next-project/prisma/migrations/add_withdrawals_wechat_id.sql delete mode 100644 next-project/prisma/schema.prisma delete mode 100644 next-project/public/apple-icon.png delete mode 100644 next-project/public/assets/.gitkeep delete mode 100644 next-project/public/assets/avatars/1770295261234_oa16yh.jpeg delete mode 100644 next-project/public/book-chapters.json delete mode 100644 next-project/public/icon-dark-32x32.png delete mode 100644 next-project/public/icon-light-32x32.png delete mode 100644 next-project/public/icon.svg delete mode 100644 next-project/public/images/alipay.png delete mode 100644 next-project/public/images/b05c111ec1aefab166379b5d146b3efc.jpg delete mode 100644 next-project/public/images/image.png delete mode 100644 next-project/public/images/party-group-qr.png delete mode 100644 next-project/public/images/wechat-pay.png delete mode 100644 next-project/public/placeholder-logo.png delete mode 100644 next-project/public/placeholder-logo.svg delete mode 100644 next-project/public/placeholder-user.jpg delete mode 100644 next-project/public/placeholder.jpg delete mode 100644 next-project/public/placeholder.svg delete mode 100644 next-project/quick-push.sh delete mode 100644 next-project/quick_deploy.sh delete mode 100644 next-project/redeploy.sh delete mode 100644 next-project/scripts/__pycache__/deploy.cpython-37.pyc delete mode 100644 next-project/scripts/__pycache__/deploy_baota_pure_api.cpython-311.pyc delete mode 100644 next-project/scripts/__pycache__/deploy_soul.cpython-311.pyc delete mode 100644 next-project/scripts/__pycache__/deploy_soul.cpython-38.pyc delete mode 100644 next-project/scripts/auto-unbind-expired-simple.js delete mode 100644 next-project/scripts/auto-unbind-expired.js delete mode 100644 next-project/scripts/check-withdrawals-data.sql delete mode 100644 next-project/scripts/check_deployment.py delete mode 100644 next-project/scripts/clean-standalone.js delete mode 100644 next-project/scripts/devlopTest.py delete mode 100644 next-project/scripts/fix-binding-styles.txt delete mode 100644 next-project/scripts/fix-earnings-detail-styles.py delete mode 100644 next-project/scripts/migrate-chapters-to-db.js delete mode 100644 next-project/scripts/migrate-to-prisma.js delete mode 100644 next-project/scripts/migrate_binding_fields.py delete mode 100644 next-project/scripts/migrate_db_simple.py delete mode 100644 next-project/scripts/migration-add-binding-fields.sql delete mode 100644 next-project/scripts/optimize-referral-indexes.sql delete mode 100644 next-project/scripts/prepare-standalone.js delete mode 100644 next-project/scripts/remove-referred-by-field-auto.py delete mode 100644 next-project/scripts/remove-referred-by-field.py delete mode 100644 next-project/scripts/remove-referred-by-field.sql delete mode 100644 next-project/scripts/start-standalone.js delete mode 100644 next-project/scripts/sync-book-content.js delete mode 100644 next-project/scripts/test-referral-config.js delete mode 100644 next-project/scripts/test-referral-flow.js delete mode 100644 next-project/scripts/test-withdrawals-api.js delete mode 100644 next-project/scripts/verify-withdrawal-data.sql delete mode 100644 next-project/scripts/write-standalone-warning.js delete mode 100644 next-project/setup-github-token.sh delete mode 100644 next-project/styles/globals.css delete mode 100644 next-project/tsconfig.json delete mode 100644 next-project/tsconfig.tsbuildinfo diff --git a/.gitignore b/.gitignore index 3fa25b17..461ee4be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ soul-api/wechat/info.log +next-project diff --git a/next-project/.env b/next-project/.env deleted file mode 100644 index 71d3ff15..00000000 --- a/next-project/.env +++ /dev/null @@ -1,3 +0,0 @@ -# Prisma MySQL 数据库连接 -# Format: mysql://USER:PASSWORD@HOST:PORT/DATABASE -DATABASE_URL="mysql://cdb_outerroot:Zhiqun1984@56b4c23f6853c.gz.cdb.myqcloud.com:14413/soul_miniprogram" \ No newline at end of file diff --git a/next-project/.env.port.example b/next-project/.env.port.example deleted file mode 100644 index 8607bfda..00000000 --- a/next-project/.env.port.example +++ /dev/null @@ -1,32 +0,0 @@ -# 端口配置示例 -# 复制此文件为 .env 并根据实际情况修改 - -# ======================================== -# 应用端口配置(避免多项目端口冲突) -# ======================================== - -# 方式1: 本地开发启动(pnpm start) -# 在终端中设置: -# Windows PowerShell: $env:PORT=30006; pnpm start -# Windows CMD: set PORT=30006 && pnpm start -# Linux/Mac: PORT=30006 pnpm start - -# 方式2: Docker Compose 部署 -# 设置 APP_PORT 变量,容器内外端口都使用此值 -APP_PORT=30006 - -# 方式3: Docker 直接运行 -# docker run -e PORT=3007 -p 3007:3007 soul-book - -# ======================================== -# 多项目端口规划建议 -# ======================================== -# soul-book: 30006 -# other-project: 3007 -# api-service: 3008 -# ... - -# 注意: -# 1. 修改端口后,需要同步更新支付回调地址等配置 -# 2. 部署到宝塔面板时,deploy_soul.py 会使用配置的端口 -# 3. 确保防火墙和反向代理配置正确 diff --git a/next-project/.github-upload-rules.md b/next-project/.github-upload-rules.md deleted file mode 100644 index fc927623..00000000 --- a/next-project/.github-upload-rules.md +++ /dev/null @@ -1,389 +0,0 @@ -# GitHub 上传规则 - -## 仓库信息 - -**仓库地址**: https://github.com/fnvtk/Mycontent -**分支**: soul-content -**完整地址**: https://github.com/fnvtk/Mycontent/tree/soul-content - ---- - -## 快速上传命令 - -### 一键上传(推荐) -```bash -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" -git add -A -git commit -m "更新内容" -git push origin soul-content -``` - -### 详细上传步骤 -```bash -# 1. 进入项目目录 -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" - -# 2. 查看状态 -git status - -# 3. 添加所有更改 -git add -A - -# 4. 提交更改(修改提交信息) -git commit -m "feat: 更新说明" - -# 5. 推送到GitHub -git push origin soul-content -``` - ---- - -## 常用提交信息模板 - -### 功能更新 -```bash -git commit -m "feat: 添加新功能 - -- 功能1 -- 功能2 -- 功能3" -``` - -### Bug修复 -```bash -git commit -m "fix: 修复问题 - -- 修复问题1 -- 修复问题2" -``` - -### 界面优化 -```bash -git commit -m "style: 界面优化 - -- 优化首页 -- 优化匹配页面 -- 统一配色" -``` - -### 文档更新 -```bash -git commit -m "docs: 更新文档 - -- 更新README -- 添加使用说明" -``` - -### 性能优化 -```bash -git commit -m "perf: 性能优化 - -- 优化加载速度 -- 优化API响应" -``` - ---- - -## Talk功能上传规则 - -### Talk内容目录 -``` -book/ -├── 附录/ -│ └── 附录1|Soul派对房精选对话.md -└── talk/ (如果有独立的talk目录) -``` - -### 上传Talk内容 -```bash -# 1. 添加talk相关文件 -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" -git add book/附录/附录1|Soul派对房精选对话.md -git add book/talk/* # 如果有talk目录 - -# 2. 提交 -git commit -m "feat: 更新Talk内容 - -- 添加新的对话记录 -- 更新精选对话" - -# 3. 推送 -git push origin soul-content -``` - ---- - -## 分支管理 - -### 查看当前分支 -```bash -git branch -``` - -### 切换分支 -```bash -git checkout soul-content -``` - -### 创建新分支 -```bash -git checkout -b new-branch-name -``` - ---- - -## 同步最新代码 - -### 拉取最新代码 -```bash -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" -git pull origin soul-content -``` - -### 查看远程仓库 -```bash -git remote -v -``` - ---- - -## 回滚操作 - -### 撤销未提交的更改 -```bash -git checkout -- <文件名> -``` - -### 撤销已提交但未推送的提交 -```bash -git reset --soft HEAD^ -``` - -### 查看提交历史 -```bash -git log --oneline -``` - ---- - -## 忽略文件 - -### .gitignore 常用配置 -``` -# 依赖 -node_modules/ -.pnp -.pnp.js - -# 测试 -coverage/ - -# 生产构建 -.next/ -out/ -build/ -dist/ - -# 环境变量 -.env -.env.local -.env.production.local -.env.development.local - -# 调试日志 -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# 操作系统 -.DS_Store -Thumbs.db - -# IDE -.vscode/ -.idea/ -*.swp -*.swo - -# 小程序 -miniprogram/node_modules/ -miniprogram/.tea/ - -# 临时文件 -*.log -*.tmp -``` - ---- - -## 紧急情况处理 - -### 推送失败(冲突) -```bash -# 1. 拉取最新代码 -git pull origin soul-content - -# 2. 解决冲突后 -git add -A -git commit -m "fix: 解决冲突" -git push origin soul-content -``` - -### 强制推送(慎用) -```bash -git push -f origin soul-content -``` - ---- - -## 标签管理 - -### 创建版本标签 -```bash -git tag -a v1.3.1 -m "完美版本:首页对齐H5,64章精准数据" -git push origin v1.3.1 -``` - -### 查看所有标签 -```bash -git tag -``` - ---- - -## 自动化脚本 - -### 快速上传脚本 (quick-push.sh) -```bash -#!/bin/bash - -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" - -echo "🚀 开始上传到GitHub..." - -# 添加所有更改 -git add -A - -# 获取提交信息 -echo "请输入提交信息(留空则使用默认信息):" -read commit_msg - -if [ -z "$commit_msg" ]; then - commit_msg="update: 更新内容 $(date '+%Y-%m-%d %H:%M:%S')" -fi - -# 提交 -git commit -m "$commit_msg" - -# 推送 -git push origin soul-content - -echo "✅ 上传完成!" -echo "📝 提交信息:$commit_msg" -echo "🔗 查看:https://github.com/fnvtk/Mycontent/tree/soul-content" -``` - -### 使用方法 -```bash -# 1. 赋予执行权限 -chmod +x quick-push.sh - -# 2. 执行脚本 -./quick-push.sh -``` - ---- - -## 常见问题 - -### Q: 推送时要求输入用户名密码? -**A**: 使用GitHub个人访问令牌(Personal Access Token) -```bash -# 设置远程仓库URL(使用token) -git remote set-url origin https://@github.com/fnvtk/Mycontent.git -``` - -### Q: 文件太大无法推送? -**A**: 使用Git LFS -```bash -git lfs install -git lfs track "*.大文件" -git add .gitattributes -``` - -### Q: 如何删除远程分支? -**A**: -```bash -git push origin --delete branch-name -``` - ---- - -## 项目结构 - -``` -一场soul的创业实验/ -├── app/ # Next.js应用 -├── book/ # 书籍内容(64章) -│ ├── 序言|... -│ ├── 第一篇|真实的人/ -│ ├── 第二篇|真实的行业/ -│ ├── 第三篇|真实的错误/ -│ ├── 第四篇|真实的赚钱/ -│ ├── 第五篇|真实的社会/ -│ ├── 尾声|... -│ └── 附录/ -│ └── 附录1|Soul派对房精选对话.md ← Talk内容 -├── miniprogram/ # 微信小程序 -├── components/ # 组件 -├── lib/ # 工具库 -├── public/ # 静态资源 -│ └── book-chapters.json # 64章数据 -├── scripts/ # 脚本 -│ └── sync-book-content.js -├── .gitignore # Git忽略配置 -├── package.json # 项目配置 -└── README.md # 项目说明 -``` - ---- - -## 下次上传流程 - -### 简单三步: -```bash -# 1. 进入目录 -cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" - -# 2. 添加并提交 -git add -A && git commit -m "更新内容" - -# 3. 推送 -git push origin soul-content -``` - -### 或使用别名(更快) -```bash -# 添加到 ~/.zshrc 或 ~/.bashrc -alias soul-push='cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验" && git add -A && git commit -m "update: 更新内容" && git push origin soul-content' - -# 使用 -soul-push -``` - ---- - -## 重要提示 - -1. ⚠️ **推送前检查**:确保没有敏感信息(密钥、密码等) -2. 📝 **提交信息**:写清楚每次更改的内容 -3. 🔄 **定期备份**:重要节点创建标签 -4. 🚫 **不要推送**:node_modules、.env等文件 -5. ✅ **推送后验证**:访问GitHub确认更新成功 - ---- - -**仓库地址**: https://github.com/fnvtk/Mycontent/tree/soul-content - -**创建时间**: 2026年1月14日 -**最后更新**: v1.3.1 diff --git a/next-project/.gitignore b/next-project/.gitignore deleted file mode 100644 index d52b10a0..00000000 --- a/next-project/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -node_modules/ -.next/ -.env.local -.DS_Store -.trae/ -*.log -node_modules - -# 部署配置(含服务器信息,勿提交) -deploy_config.json -scripts/deploy_config.json - -/lib/generated/prisma diff --git a/next-project/DEPLOYMENT.md b/next-project/DEPLOYMENT.md deleted file mode 100644 index 3de3a625..00000000 --- a/next-project/DEPLOYMENT.md +++ /dev/null @@ -1,202 +0,0 @@ -# 部署指南 - -## 整站与后台管理端 - -本项目是**一个 Next.js 应用**,前台 H5、后台管理、API 都在同一套代码里: - -- **前台**:`/`、`/chapters`、`/read/*`、`/my`、`/match` 等 -- **后台管理端**:`/admin`、`/admin/login`、`/admin/settings`、`/admin/users` 等 -- **API**:`/api/*`(含 `/api/admin/*`) - -**部署一次 = 前台 + 后台 + API 一起上线。** 后台无需单独部署,上线后访问: - -- 后台首页:`https://你的域名/admin` -- 后台登录:`https://你的域名/admin/login`(账号见项目文档,如 `admin` / `key123456`) - ---- - -## 项目内已有的部署配置 - -| 类型 | 文件/目录 | 说明 | -|------|------------|------| -| 总览文档 | `DEPLOYMENT.md`(本文件) | 部署步骤、环境变量、支付回调 | -| Next 配置 | `next.config.mjs` | `output: 'standalone'` 供宝塔 standalone 部署使用 | -| **宝塔部署(统一入口)** | **`scripts/devlop.py`** | **本地打包 → SSH 上传解压 → 宝塔 API 重启 Node 项目(Windows/Mac/Linux 通用)** | -| 宝塔 API 模块 | `scripts/deploy_baota_pure_api.py` | 被 devlop.py 内部调用(重启 Node);也可单独用于仅重启或触发计划任务 | -| 宝塔方案说明 | `开发文档/8、部署/Next.js宝塔部署方案.md` | 宝塔首次准备与日常部署步骤 | -| 宝塔自动化 | `开发文档/8、部署/Next.js自动化部署流程.md` | GitHub Webhook + 宝塔,推送即自动部署 | - -无 `vercel.json` 时,Vercel 会按默认规则部署本仓库;若需自定义路由或头信息,可再加 `vercel.json`。 - ---- - -## 宝塔部署(统一使用 devlop.py) - -**日常部署**统一使用 **`scripts/devlop.py`**:本地打包 → SSH 上传解压 → 宝塔 API 重启,Windows / Mac / Linux 通用,不依赖 sshpass 或 shell。 - -### 1. 安装依赖 - -\`\`\`bash -pip install -r requirements-deploy.txt -\`\`\` - -### 2. 配置(可选) - -脚本默认使用 `.cursorrules` 中的服务器信息(43.139.27.93、root、项目路径 /www/wwwroot/soul 等)。如需覆盖,可设置环境变量: - -- `DEPLOY_HOST`、`DEPLOY_USER`、`DEPLOY_PASSWORD` 或 `DEPLOY_SSH_KEY` -- `DEPLOY_PROJECT_PATH`(如 /www/wwwroot/soul) -- `BAOTA_PANEL_URL`、`BAOTA_API_KEY` -- `DEPLOY_PM2_APP`(默认 soul) - -### 3. 执行部署 - -在**项目根目录**执行: - -\`\`\`bash -python scripts/devlop.py -\`\`\` - -- **流程**:本地 `pnpm build` → 打包 `.next/standalone`(含 static、public、ecosystem.config.cjs)→ SSH 上传并解压到服务器 → **宝塔 API 重启 Node 项目**。 -- **参数**:`--no-build` 跳过构建;`--no-upload` 仅构建+打包;`--no-api` 上传后不调 API 重启。 - -部署完成后访问: - -- 前台:`https://soul.quwanzhi.com` -- 后台:`https://soul.quwanzhi.com/admin` - -### 4. 仅重启 Node(不上传代码) - -若只需在宝塔上重启 Node 项目(代码已通过其他方式更新),可单独使用宝塔 API 模块: - -\`\`\`bash -pip install requests -python scripts/deploy_baota_pure_api.py # 重启 Node 项目 soul -python scripts/deploy_baota_pure_api.py --create-dir # 并创建项目目录 -python scripts/deploy_baota_pure_api.py --task-id 1 # 触发计划任务 ID=1 -\`\`\` - -### 5. 首次在宝塔上准备 - -若服务器上尚未有代码,需先在宝塔上: - -1. 在网站目录(如 `/www/wwwroot/soul`)创建目录,或从本地上传/克隆代码。 -2. 在宝塔「PM2 管理器」中新增项目:项目目录选该路径,启动文件为 `node server.js`,环境变量 `PORT=30006`。 -3. 配置 Nginx 反向代理到 `127.0.0.1:30006`,并绑定域名 soul.quwanzhi.com。 -4. 之后日常部署执行 `python scripts/devlop.py` 即可。 - ---- - -## 生产环境部署步骤 - -### 1. Vercel部署 - -\`\`\`bash -# 安装Vercel CLI -npm install -g vercel - -# 登录Vercel -vercel login - -# 部署项目 -vercel --prod -\`\`\` - -### 2. 环境变量配置 - -在Vercel项目设置中添加以下环境变量: - -**支付宝配置:** -- `ALIPAY_PARTNER_ID`: 2088511801157159 -- `ALIPAY_KEY`: lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp -- `ALIPAY_APP_ID`: wx432c93e275548671 -- `ALIPAY_RETURN_URL`: https://your-domain.com/payment/success -- `ALIPAY_NOTIFY_URL`: https://your-domain.com/api/payment/alipay/notify - -**微信支付配置:** -- `WECHAT_APP_ID`: wx432c93e275548671 -- `WECHAT_APP_SECRET`: 25b7e7fdb7998e5107e242ebb6ddabd0 -- `WECHAT_MCH_ID`: 1318592501 -- `WECHAT_API_KEY`: wx3e31b068be59ddc131b068be59ddc2 -- `WECHAT_NOTIFY_URL`: https://your-domain.com/api/payment/wechat/notify - -**基础配置:** -- `NEXT_PUBLIC_BASE_URL`: https://your-domain.com - -### 3. 域名配置 - -1. 在Vercel项目设置中绑定自定义域名 -2. 配置DNS记录指向Vercel -3. 启用HTTPS(Vercel自动配置SSL证书) - -### 4. 支付回调配置 - -**支付宝配置:** -1. 登录支付宝开放平台 -2. 在应用详情中配置异步通知地址:`https://your-domain.com/api/payment/alipay/notify` -3. 配置同步返回地址:`https://your-domain.com/payment/success` - -**微信支付配置:** -1. 登录微信商户平台 -2. 在产品中心配置支付回调URL:`https://your-domain.com/api/payment/wechat/notify` -3. 添加支付授权域名:`your-domain.com` - -**提现(商家转账到零钱):** 详见 `开发文档/提现功能完整技术文档.md`。需配置: -- `WECHAT_MCH_ID`:商户号 -- `WECHAT_APP_ID`:小程序/公众号 AppID(如 `wxb8bbb2b10dec74aa`) -- `WECHAT_API_V3_KEY` 或 `WECHAT_MCH_KEY`:APIv3 密钥(32 字节,用于回调解密) -- `WECHAT_KEY_PATH` 或 `WECHAT_MCH_PRIVATE_KEY_PATH`:商户私钥文件路径(apiclient_key.pem) -- `WECHAT_MCH_CERT_SERIAL_NO`:商户证书序列号(OpenSSL 从 apiclient_cert.pem 提取) -- 商户平台需配置:商家转账到零钱、转账结果通知 URL:`https://你的域名/api/payment/wechat/transfer/notify` - -### 5. 测试流程 - -1. 创建测试订单 -2. 使用沙箱环境测试支付宝支付 -3. 使用微信开发者工具测试微信支付 -4. 验证回调接口正常接收 -5. 确认订单状态更新正确 -6. 验证内容解锁功能 - -### 6. 监控和日志 - -- 在Vercel Dashboard查看部署日志 -- 使用Vercel Analytics监控访问数据 -- 配置错误告警通知 - -## 本地开发 - -\`\`\`bash -# 安装依赖 -npm install - -# 启动开发服务器 -npm run dev - -# 访问 http://localhost:3000 -\`\`\` - -### Windows 本地执行 `pnpm build` 报 EPERM symlink - -本项目使用 `output: 'standalone'`,构建时 Next.js 会创建符号链接。**Windows 默认不允许普通用户创建符号链接**,会报错: - -- `EPERM: operation not permitted, symlink ... -> .next\standalone\node_modules\...` - -**可选做法(任选其一):** - -1. **开启 Windows 开发者模式(推荐,一劳永逸)** - - 设置 → 隐私和安全性 → 针对开发人员 → **开发人员模式** 打开 - - 开启后无需管理员即可创建符号链接,本地 `pnpm build` 可正常完成。 - -2. **以管理员身份运行终端再执行构建** - - 右键 Cursor/终端 → “以管理员身份运行”,在项目根目录执行 `pnpm build`。 - -若只做部署、不在本机打 standalone 包,可用 `python scripts/devlop.py --no-build` 跳过构建后上传已有包,或由服务器/计划任务在服务器上执行构建。 - -## 注意事项 - -1. 生产环境必须使用HTTPS -2. 定期更新支付密钥 -3. 保护环境变量安全 -4. 备份用户数据 -5. 监控支付异常 diff --git a/next-project/PRISMA_迁移完成.md b/next-project/PRISMA_迁移完成.md deleted file mode 100644 index adb3bdba..00000000 --- a/next-project/PRISMA_迁移完成.md +++ /dev/null @@ -1,146 +0,0 @@ -# 🎉 Prisma ORM 迁移完成! - -## ✅ 核心工作已完成 - -### 📦 已完成(12个核心文件) - -#### 基础设施(3个) -- ✅ `prisma/schema.prisma` - 数据库模型(12个表) -- ✅ `lib/prisma.ts` - Prisma Client 单例 -- ✅ `lib/prisma-helpers.ts` - 辅助函数库 - -#### 核心 API(9个) -1. ✅ `/api/wechat/login` - 微信登录 -2. ✅ `/api/user/profile` - 用户资料 -3. ✅ `/api/user/update` - 更新用户 -4. ✅ `/api/withdraw` - 提现申请 -5. ✅ `/api/admin/withdrawals` - **提现审批(修复 undefined.length)** -6. ✅ `/api/referral/data` - 分销数据 -7. ✅ `/api/referral/bind` - 推荐绑定 -8. ✅ `/api/book/chapters` - 章节管理 -9. ✅ `/api/db/config` - 系统配置 - ---- - -## 🎯 关键成就 - -### 1. 安全问题全部解决 ✅ -- ✅ **SQL注入风险:100% 消除** -- ✅ **undefined.length Bug:彻底修复** -- ✅ **类型安全:TypeScript 严格检查** - -### 2. 核心功能已迁移 ✅ -- ✅ 登录注册系统 -- ✅ 用户资料管理 -- ✅ **提现系统(重点)** -- ✅ 分销推荐系统 -- ✅ 书籍章节管理 -- ✅ 系统配置管理 - -### 3. 代码质量提升 ✅ -- ✅ 可读性提升 80% -- ✅ 维护成本降低 60% -- ✅ 开发效率提升 50% - ---- - -## 🚀 立即测试(必须) - -### 步骤 1:重启服务器 -```bash -# 停止当前服务器(Ctrl+C) -pnpm dev -``` - -### 步骤 2:测试核心功能 - -#### ✅ 测试提现功能(重点) -1. **小程序端**: - - 进入分销中心 → 点击提现 → 输入金额 → 提交 - -2. **后台端**: - - 交易中心 → 提现审核 → 批准/拒绝 - -3. **验证点**: - - ⚠️ **控制台是否还有 `undefined.length` 错误?** - - ✅ 提现状态是否正确更新? - - ✅ 用户已提现金额是否正确? - -#### ✅ 测试登录和用户 -- 微信登录是否正常? -- 修改昵称是否保存成功? -- 用户资料是否正确显示? - -#### ✅ 测试分销数据 -- 绑定用户数是否正确? -- 累计佣金是否准确? -- 收益明细是否显示? - ---- - -## 📊 迁移统计 - -| 项目 | 数量 | 状态 | -|------|------|------| -| 核心 API | 9个 | ✅ 完成 | -| 基础文件 | 3个 | ✅ 完成 | -| 文档 | 4个 | ✅ 完成 | -| 待迁移(可选) | 24个 | ⏳ 按需迁移 | - -**核心完成度**:✅ **100%**(所有关键业务已迁移) - ---- - -## 📚 详细文档 - -1. **`开发文档/8、部署/Prisma ORM迁移最终报告.md`** - - 完整的迁移报告 - - 技术细节和代码对比 - - 常见问题解答 - -2. **`开发文档/8、部署/Prisma ORM完整迁移总结.md`** - - 快速迁移模板(4种) - - 剩余24个API迁移指南 - - 分类优先级列表 - -3. **`开发文档/8、部署/Prisma ORM迁移进度.md`** - - 进度跟踪 - - 文件对比示例 - ---- - -## 💡 后续工作(可选) - -### 短期(1周内) -1. ✅ 测试核心功能 -2. 根据反馈调整 -3. 逐步迁移1-2个常用API - -### 长期(按需) -4. 迁移剩余24个辅助API -5. 统一使用 Prisma -6. 删除 `lib/db.ts` - ---- - -## 🎊 结论 - -### ✅ 项目现状:可以安全投入生产 - -- **核心功能**:全部使用 Prisma(安全可靠) -- **辅助功能**:保留旧代码(兼容性好) -- **新增功能**:优先使用 Prisma(最佳实践) - -### 🎉 主要收益 - -1. **安全性**:彻底消除 SQL 注入和 undefined.length bug -2. **可靠性**:类型安全,减少运行时错误 -3. **效率**:开发速度提升,维护成本降低 - ---- - -**完成时间**:2026-02-04 -**工作量**:约 3-4 小时 -**状态**:✅ **核心迁移完成,可以测试和上线!** - -🚀 **现在就重启服务器,开始测试吧!** diff --git a/next-project/README.md b/next-project/README.md deleted file mode 100644 index ae009582..00000000 --- a/next-project/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 一场SOUL的创业实验 - -## 项目简介 -这是一个基于 Soul 派对房真实商业故事开发的知识付费与分销系统。 - -### 核心功能 -- 📚 **电子书阅读**: 每日更新的真实商业案例。 -- 🤝 **找伙伴**: 匹配志同道合的创业合伙人。 -- 💰 **分销系统**: 90% 高额佣金,推广赚收益。 -- 👤 **个人中心**: 账号绑定、收益提现、阅读足迹。 - -## 技术开发 -本项目由 **卡若** 开发,核心逻辑与私域系统由 **存客宝** 提供技术支持。 - ---- -*版权所有 © 2026 卡若 & 存客宝* diff --git a/next-project/SYNC_LOG.md b/next-project/SYNC_LOG.md deleted file mode 100644 index 27097d7b..00000000 --- a/next-project/SYNC_LOG.md +++ /dev/null @@ -1 +0,0 @@ -Mon Dec 29 18:11:24 CST 2025 diff --git a/next-project/addons/Universal_Payment_Module copy/.cursorrules b/next-project/addons/Universal_Payment_Module copy/.cursorrules deleted file mode 100644 index e9610e90..00000000 --- a/next-project/addons/Universal_Payment_Module copy/.cursorrules +++ /dev/null @@ -1,68 +0,0 @@ -# Universal Payment Module - Cursor 规则 -# 将此文件放在使用支付模块的项目根目录 - -## 角色设定 -你是一位精通全球支付架构的资深全栈工程师,专注于支付网关集成、安全合规和高可用设计。 - -当用户提及"支付模块"、"支付功能"、"接入支付"时,请参考 `Universal_Payment_Module` 目录中的设计文档。 - -## 核心原则 - -### 1. 配置驱动 -- 所有支付密钥通过环境变量配置,绝不硬编码 -- 使用 `.env` 文件管理配置 -- 支持多环境切换 (development/staging/production) - -### 2. 工厂模式 -- 使用 `PaymentFactory` 统一管理支付网关 -- 每个网关实现统一的 `AbstractGateway` 接口 -- 支持: alipay/wechat/paypal/stripe/usdt - -### 3. 安全优先 -- 所有回调必须验证签名 -- 必须验证支付金额与订单金额匹配 -- 使用 HTTPS,敏感数据脱敏 - -### 4. 幂等性 -- 支付回调必须支持重复调用 -- 使用分布式锁防止并发问题 - -## API 接口 -``` -POST /api/payment/create_order - 创建订单 -POST /api/payment/checkout - 发起支付 -GET /api/payment/status/{sn} - 查询状态 -POST /api/payment/notify/{gw} - 回调通知 -``` - -## 统一响应格式 -```json -{ - "code": 200, - "message": "success", - "data": { ... } -} -``` - -## 数据库 -- orders - 订单表 -- pay_trades - 交易流水表 -- 金额单位: 数据库用分,API用元 - -## 卡若支付配置 -- 微信商户号: 1318592501 -- 支付宝PID: 2088511801157159 -- 详细配置见 `4_卡若配置/.env.example` - -## 禁止事项 -❌ 密钥硬编码 -❌ 跳过签名验证 -❌ 信任前端金额 -❌ 回调不做幂等 -❌ 使用 HTTP - -## 参考文档 -- API定义: `1_核心设计_通用协议/API接口定义.md` -- 数据模型: `1_核心设计_通用协议/业务逻辑与模型.md` -- 安全规范: `1_核心设计_通用协议/安全与合规.md` -- AI指令: `2_智能对接_AI指令/通用集成指令.md` diff --git a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md b/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md deleted file mode 100644 index 9487549d..00000000 --- a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/API接口定义.md +++ /dev/null @@ -1,382 +0,0 @@ -# 通用支付模块 API 接口定义 (Universal Payment API) v4.0 - -> 无论后端使用何种语言(Python/Node/Go/Java/PHP),请严格实现以下 RESTful 接口 - -## 🎯 设计原则 - -1. **RESTful 风格**: 资源命名统一,动词语义清晰 -2. **统一响应格式**: 所有接口返回 `{code, message, data}` 结构 -3. **幂等性**: 重复请求不产生副作用 -4. **安全性**: 敏感操作需签名验证 - ---- - -## 📦 统一响应格式 - -### 成功响应 -```json -{ - "code": 200, - "message": "success", - "data": { ... } -} -``` - -### 错误响应 -```json -{ - "code": 400, - "message": "参数错误:order_sn 不能为空", - "data": null -} -``` - -### 常用错误码 -| Code | 含义 | -|:---|:---| -| 200 | 成功 | -| 400 | 请求参数错误 | -| 401 | 未授权/登录过期 | -| 403 | 无权限 | -| 404 | 资源不存在 | -| 409 | 状态冲突 (如重复支付) | -| 500 | 服务器内部错误 | - ---- - -## 1. 核心交易接口 (Core Transaction) - -### 1.1 创建订单 -业务系统调用,创建一个待支付订单。 - -```http -POST /api/payment/create_order -Content-Type: application/json -Authorization: Bearer {token} -``` - -**Request Body**: -```json -{ - "user_id": "u1001", // [必填] 用户ID - "title": "VIP会员月卡", // [必填] 订单标题 (≤30字符) - "amount": 99.00, // [必填] 金额 (单位: 元) - "currency": "CNY", // [可选] 币种,默认 CNY - "product_id": "vip_monthly", // [可选] 商品ID - "product_type": "membership", // [可选] 商品类型 - "extra_params": { // [可选] 扩展参数 (会透传到回调) - "coupon_id": "C001", - "referrer": "user_123" - } -} -``` - -**Response**: -```json -{ - "code": 200, - "message": "success", - "data": { - "order_sn": "202401170001", // 系统生成的订单号 - "status": "created", // 订单状态 - "amount": 99.00, - "expire_at": "2024-01-17T11:30:00Z" // 订单过期时间 - } -} -``` - ---- - -### 1.2 发起支付 (收银台) -用户选择支付方式后,获取支付参数。 - -```http -POST /api/payment/checkout -Content-Type: application/json -Authorization: Bearer {token} -``` - -**Request Body**: -```json -{ - "order_sn": "202401170001", // [必填] 订单号 - "gateway": "wechat_jsapi", // [必填] 支付网关 (见下方枚举) - "return_url": "https://...", // [可选] 支付成功后跳转地址 - "openid": "oXxx...", // [条件] 微信JSAPI必填 - "coin_amount": 0 // [可选] 使用虚拟币抵扣金额 -} -``` - -**Gateway 支付网关枚举**: -| Gateway | 说明 | 返回类型 | -|:---|:---|:---| -| `alipay_web` | 支付宝PC网页 | url (跳转) | -| `alipay_wap` | 支付宝H5 | url (跳转) | -| `alipay_qr` | 支付宝扫码 | qrcode | -| `wechat_native` | 微信扫码 | qrcode | -| `wechat_jsapi` | 微信公众号/小程序 | json (SDK参数) | -| `wechat_h5` | 微信H5 | url (跳转) | -| `wechat_app` | 微信APP | json (SDK参数) | -| `paypal` | PayPal | url (跳转) | -| `stripe` | Stripe | url (Checkout Session) | -| `usdt` | USDT-TRC20 | address (钱包地址) | -| `coin` | 纯虚拟币支付 | direct (直接完成) | - -**Response**: -```json -{ - "code": 200, - "message": "success", - "data": { - "trade_sn": "T20240117100001", // 交易流水号 - "type": "qrcode", // 响应类型: url/qrcode/json/address/direct - "payload": "weixin://wxpay/...",// 支付数据 (根据type不同) - "expiration": 1800, // 过期时间 (秒) - "amount": 99.00, // 实际支付金额 (扣除抵扣后) - "coin_deducted": 0 // 虚拟币抵扣金额 - } -} -``` - -**不同 type 的 payload 格式**: - -```javascript -// type: "url" - 跳转链接 -payload: "https://openapi.alipay.com/gateway.do?..." - -// type: "qrcode" - 二维码内容 -payload: "weixin://wxpay/bizpayurl?pr=xxx" - -// type: "json" - SDK调起参数 (微信JSAPI) -payload: { - "appId": "wx...", - "timeStamp": "1705470600", - "nonceStr": "xxx", - "package": "prepay_id=wx...", - "signType": "RSA", - "paySign": "xxx" -} - -// type: "address" - 加密货币地址 -payload: { - "address": "TXxx...", - "amount_usdt": 13.88, - "memo": "202401170001" -} - -// type: "direct" - 直接完成 (纯虚拟币支付) -payload: { "status": "paid" } -``` - ---- - -### 1.3 查询订单状态 -前端轮询使用,判断支付是否完成。 - -```http -GET /api/payment/status/{order_sn} -Authorization: Bearer {token} -``` - -**Response**: -```json -{ - "code": 200, - "message": "success", - "data": { - "order_sn": "202401170001", - "status": "paid", // created/paying/paid/closed/refunded - "paid_amount": 99.00, - "paid_at": "2024-01-17T10:05:00Z", - "payment_method": "wechat_jsapi", - "trade_sn": "T20240117100001" - } -} -``` - -**订单状态机**: -``` -created → paying → paid → (refunded) - ↓ ↓ - closed closed -``` - ---- - -### 1.4 关闭订单 -主动关闭未支付的订单。 - -```http -POST /api/payment/close/{order_sn} -Authorization: Bearer {token} -``` - -**Response**: -```json -{ - "code": 200, - "message": "success", - "data": { - "order_sn": "202401170001", - "status": "closed", - "closed_at": "2024-01-17T10:10:00Z" - } -} -``` - ---- - -## 2. 回调通知接口 (Webhook) - -### 2.1 统一回调入口 -接收第三方支付平台的异步通知。 - -```http -POST /api/payment/notify/{gateway} -``` - -**Path Params**: -- `gateway`: `alipay` / `wechat` / `paypal` / `stripe` / `nowpayments` - -**处理逻辑**: -1. 根据 gateway 加载对应驱动 -2. 验签 (Verify Signature) -3. 幂等性检查 (防重复处理) -4. 更新订单状态 -5. 触发业务回调 (发货/开通权限等) -6. 返回平台所需响应 - -**返回格式**: -``` -# 支付宝 -success - -# 微信 - - -# Stripe -HTTP 200 OK - -# PayPal -HTTP 200 OK -``` - ---- - -### 2.2 同步返回 (Return) -用户支付完成后的页面跳转。 - -```http -GET /api/payment/return/{gateway} -``` - -**Query Params**: 各平台不同,由平台自动附加 - -**处理逻辑**: -1. 解析回传参数 -2. 验签 -3. 重定向到成功页面 - ---- - -## 3. 辅助接口 - -### 3.1 获取可用支付方式 -```http -GET /api/payment/methods -``` - -**Response**: -```json -{ - "code": 200, - "data": { - "methods": [ - { - "gateway": "wechat_jsapi", - "name": "微信支付", - "icon": "/icons/wechat.png", - "enabled": true, - "available": true // 当前环境是否可用 (如微信内) - }, - { - "gateway": "alipay_wap", - "name": "支付宝", - "icon": "/icons/alipay.png", - "enabled": true, - "available": true - } - ] - } -} -``` - -### 3.2 获取汇率 -```http -GET /api/payment/exchange_rate?from=CNY&to=USD -``` - -**Response**: -```json -{ - "code": 200, - "data": { - "from": "CNY", - "to": "USD", - "rate": 0.139, - "updated_at": "2024-01-17T00:00:00Z" - } -} -``` - ---- - -## 4. 管理接口 (Admin) - -### 4.1 订单列表 -```http -GET /api/admin/payment/orders?page=1&limit=20&status=paid -Authorization: Bearer {admin_token} -``` - -### 4.2 交易流水列表 -```http -GET /api/admin/payment/trades?page=1&limit=20 -Authorization: Bearer {admin_token} -``` - -### 4.3 发起退款 -```http -POST /api/admin/payment/refund -Authorization: Bearer {admin_token} -Content-Type: application/json - -{ - "trade_sn": "T20240117100001", - "amount": 99.00, - "reason": "用户申请退款" -} -``` - ---- - -## 5. 接口签名规范 (可选) - -对于安全要求高的场景,可启用接口签名: - -```javascript -// 请求头 -X-Sign: sha256(timestamp + nonce + body + secret) -X-Timestamp: 1705470600 -X-Nonce: abc123 -``` - ---- - -## 📌 注意事项 - -1. **金额单位**: 所有金额均以**元**为单位,小数点后2位 -2. **时间格式**: ISO 8601 格式 `YYYY-MM-DDTHH:mm:ssZ` -3. **字符编码**: UTF-8 -4. **HTTPS**: 生产环境必须使用 HTTPS -5. **幂等性**: 相同订单号重复请求返回相同结果 diff --git a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md b/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md deleted file mode 100644 index b93a2250..00000000 --- a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md +++ /dev/null @@ -1,396 +0,0 @@ -# 业务逻辑与数据模型 (Business Logic & Data Model) v4.0 - -> 定义支付系统的核心数据结构和业务流程 - -## 📊 数据库表结构 - -### 1. 订单表 (orders) - -存储业务订单信息,与支付解耦。 - -```sql -CREATE TABLE `orders` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `sn` VARCHAR(32) NOT NULL COMMENT '订单号 (业务唯一)', - `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', - `title` VARCHAR(128) NOT NULL COMMENT '订单标题', - `price_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单原价 (分)', - `pay_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '应付金额 (分)', - `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型', - `status` VARCHAR(20) NOT NULL DEFAULT 'created' COMMENT '状态: created/paying/paid/closed/refunded', - `product_id` VARCHAR(64) DEFAULT NULL COMMENT '商品ID', - `product_type` VARCHAR(32) DEFAULT NULL COMMENT '商品类型', - `extra_data` JSON DEFAULT NULL COMMENT '扩展数据', - `paid_at` DATETIME DEFAULT NULL COMMENT '支付时间', - `closed_at` DATETIME DEFAULT NULL COMMENT '关闭时间', - `expired_at` DATETIME DEFAULT NULL COMMENT '过期时间', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_sn` (`sn`), - KEY `idx_user_id` (`user_id`), - KEY `idx_status` (`status`), - KEY `idx_created_at` (`created_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -``` - -### 2. 交易流水表 (pay_trades) - -记录每一次支付尝试,一个订单可能有多次交易。 - -```sql -CREATE TABLE `pay_trades` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `trade_sn` VARCHAR(32) NOT NULL COMMENT '交易流水号 (系统生成)', - `order_sn` VARCHAR(32) NOT NULL COMMENT '关联订单号', - `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', - `title` VARCHAR(128) NOT NULL COMMENT '交易标题', - `amount` BIGINT UNSIGNED NOT NULL COMMENT '交易金额 (分)', - `cash_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '现金支付金额 (分)', - `coin_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '虚拟币抵扣金额', - `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型', - `platform` VARCHAR(32) NOT NULL COMMENT '支付平台: alipay/wechat/paypal/stripe/usdt/coin', - `platform_type` VARCHAR(32) DEFAULT NULL COMMENT '平台子类型: web/wap/jsapi/native/h5/app', - `platform_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台交易号', - `platform_created_params` JSON DEFAULT NULL COMMENT '发送给平台的参数', - `platform_created_result` JSON DEFAULT NULL COMMENT '平台返回的结果', - `status` VARCHAR(20) NOT NULL DEFAULT 'paying' COMMENT '状态: paying/paid/closed/refunded', - `type` VARCHAR(20) NOT NULL DEFAULT 'purchase' COMMENT '类型: purchase(购买)/recharge(充值)', - `pay_time` DATETIME DEFAULT NULL COMMENT '支付时间', - `notify_data` JSON DEFAULT NULL COMMENT '回调原始数据', - `seller_id` VARCHAR(64) DEFAULT NULL COMMENT '卖家ID (多商户场景)', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_trade_sn` (`trade_sn`), - KEY `idx_order_sn` (`order_sn`), - KEY `idx_platform_sn` (`platform_sn`), - KEY `idx_user_id` (`user_id`), - KEY `idx_status` (`status`), - KEY `idx_created_at` (`created_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易流水表'; -``` - -### 3. 资金流水表 (cashflows) - -记录账户资金变动(可选,用于虚拟币/钱包场景)。 - -```sql -CREATE TABLE `cashflows` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `sn` VARCHAR(32) NOT NULL COMMENT '流水号', - `user_id` VARCHAR(64) NOT NULL COMMENT '用户ID', - `type` VARCHAR(20) NOT NULL COMMENT '类型: inflow(入账)/outflow(出账)', - `action` VARCHAR(32) NOT NULL COMMENT '动作: recharge/purchase/refund/transfer', - `amount` BIGINT NOT NULL COMMENT '金额 (分,正数入账负数出账)', - `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY', - `balance_before` BIGINT NOT NULL DEFAULT 0 COMMENT '变动前余额', - `balance_after` BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额', - `trade_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联交易流水号', - `order_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联订单号', - `remark` VARCHAR(256) DEFAULT NULL COMMENT '备注', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_sn` (`sn`), - KEY `idx_user_id` (`user_id`), - KEY `idx_trade_sn` (`trade_sn`), - KEY `idx_created_at` (`created_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水表'; -``` - -### 4. 退款记录表 (refunds) - -```sql -CREATE TABLE `refunds` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `refund_sn` VARCHAR(32) NOT NULL COMMENT '退款单号', - `trade_sn` VARCHAR(32) NOT NULL COMMENT '原交易流水号', - `order_sn` VARCHAR(32) NOT NULL COMMENT '原订单号', - `amount` BIGINT UNSIGNED NOT NULL COMMENT '退款金额 (分)', - `reason` VARCHAR(256) DEFAULT NULL COMMENT '退款原因', - `status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/processing/success/failed', - `platform_refund_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台退款单号', - `refunded_at` DATETIME DEFAULT NULL COMMENT '退款完成时间', - `operator_id` VARCHAR(64) DEFAULT NULL COMMENT '操作人ID', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_refund_sn` (`refund_sn`), - KEY `idx_trade_sn` (`trade_sn`), - KEY `idx_order_sn` (`order_sn`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款记录表'; -``` - ---- - -## 🔄 状态机定义 - -### 订单状态 (Order Status) - -``` -┌─────────────────────────────────────────────────┐ -│ created │ -│ │ │ -│ ┌───────────┼───────────┐ │ -│ ▼ │ ▼ │ -│ paying ────────┼───────► closed │ -│ │ │ │ -│ ▼ │ │ -│ paid ─────────┼───────► refunded │ -│ │ │ -└─────────────────────────────────────────────────┘ - -状态说明: -- created: 订单已创建,等待支付 -- paying: 支付中 (已发起支付请求) -- paid: 已支付 -- closed: 已关闭 (超时/主动取消) -- refunded: 已退款 -``` - -### 交易状态 (Trade Status) - -``` -paying → paid - ↓ ↓ -closed refunded - -状态说明: -- paying: 支付中 -- paid: 支付成功 -- closed: 交易关闭 -- refunded: 已退款 -``` - ---- - -## 🔢 编号规则 - -### 订单号 (order_sn) -``` -格式: YYYYMMDD + 6位随机数 -示例: 202401170001 - -生成规则: -1. 日期前缀保证每日唯一空间 -2. 随机数使用分布式ID生成器 -3. 支持前缀自定义 (如区分业务线) -``` - -### 交易流水号 (trade_sn) -``` -格式: T + YYYYMMDD + HHmmss + 5位随机数 -示例: T20240117100530123456 - -生成规则: -1. 前缀 T 标识交易类型 -2. 精确到秒的时间戳 -3. 5位随机数防碰撞 -``` - ---- - -## 📋 核心业务流程 - -### 1. 标准支付流程 - -```sequence -用户 -> 业务系统: 1. 提交订单 -业务系统 -> 支付模块: 2. 创建订单 (create_order) -支付模块 -> 业务系统: 3. 返回 order_sn - -用户 -> 支付模块: 4. 选择支付方式并支付 (checkout) -支付模块 -> 支付平台: 5. 创建平台交易 -支付平台 -> 支付模块: 6. 返回支付参数 -支付模块 -> 用户: 7. 返回支付数据 (二维码/跳转链接) - -用户 -> 支付平台: 8. 完成支付 -支付平台 -> 支付模块: 9. 异步回调 (notify) -支付模块 -> 支付模块: 10. 验签 + 更新状态 -支付模块 -> 业务系统: 11. 触发业务回调 (发货/开通) -``` - -### 2. 支付回调处理流程 - -```python -def handle_notify(gateway, data): - # 1. 加载对应的支付网关驱动 - driver = PaymentFactory.create(gateway) - - # 2. 验证签名 - if not driver.verify_sign(data): - raise SignatureError("签名验证失败") - - # 3. 解析回调数据 - parsed = driver.parse_notify(data) - trade_sn = parsed['trade_sn'] - - # 4. 幂等性检查 - trade = Trade.get_by_sn(trade_sn) - if trade.status == 'paid': - return driver.success_response() # 已处理过,直接返回成功 - - # 5. 金额校验 - if parsed['amount'] != trade.cash_amount: - raise AmountMismatchError("金额不匹配") - - # 6. 更新交易状态 - trade.update({ - 'status': 'paid', - 'platform_sn': parsed['platform_sn'], - 'pay_time': parsed['pay_time'], - 'notify_data': data - }) - - # 7. 更新订单状态 - order = Order.get_by_sn(trade.order_sn) - order.update({'status': 'paid', 'paid_at': now()}) - - # 8. 触发业务回调 - dispatch_event('order.paid', order) - - # 9. 返回成功响应 - return driver.success_response() -``` - -### 3. 退款流程 - -```python -def apply_refund(trade_sn, amount, reason): - trade = Trade.get_by_sn(trade_sn) - - # 1. 状态检查 - if trade.status != 'paid': - raise InvalidStatusError("只有已支付的交易可以退款") - - # 2. 创建退款记录 - refund = Refund.create({ - 'refund_sn': generate_refund_sn(), - 'trade_sn': trade_sn, - 'amount': amount, - 'reason': reason, - 'status': 'pending' - }) - - # 3. 调用平台退款接口 - driver = PaymentFactory.create(trade.platform) - result = driver.refund({ - 'trade_sn': trade_sn, - 'refund_sn': refund.refund_sn, - 'amount': amount - }) - - # 4. 更新状态 - if result.success: - refund.update({'status': 'success', 'refunded_at': now()}) - trade.update({'status': 'refunded'}) - else: - refund.update({'status': 'failed'}) - - return refund -``` - ---- - -## 🏭 工厂模式设计 - -```python -class PaymentFactory: - """支付网关工厂""" - - _drivers = { - 'alipay': AlipayGateway, - 'wechat': WechatGateway, - 'paypal': PayPalGateway, - 'stripe': StripeGateway, - 'usdt': USDTGateway, - 'coin': CoinGateway, - } - - @classmethod - def create(cls, gateway: str) -> AbstractGateway: - gateway_name = gateway.split('_')[0] # wechat_jsapi -> wechat - - if gateway_name not in cls._drivers: - raise ValueError(f"不支持的支付网关: {gateway}") - - driver_class = cls._drivers[gateway_name] - return driver_class(config=get_payment_config(gateway_name)) -``` - -```python -class AbstractGateway(ABC): - """支付网关抽象基类""" - - @abstractmethod - def create_trade(self, data: dict) -> dict: - """创建交易""" - pass - - @abstractmethod - def verify_sign(self, data: dict) -> bool: - """验证签名""" - pass - - @abstractmethod - def parse_notify(self, data: dict) -> dict: - """解析回调数据""" - pass - - @abstractmethod - def refund(self, data: dict) -> RefundResult: - """发起退款""" - pass - - @abstractmethod - def query_trade(self, trade_sn: str) -> dict: - """查询交易""" - pass - - @abstractmethod - def close_trade(self, trade_sn: str) -> bool: - """关闭交易""" - pass - - def success_response(self) -> str: - """回调成功响应""" - return "success" -``` - ---- - -## 💰 金额处理规范 - -### 1. 存储规则 -- 数据库统一使用**分**为单位 (BIGINT) -- 避免浮点数精度问题 - -### 2. 接口规则 -- API 输入输出统一使用**元**为单位 -- 内部转换: `分 = 元 × 100` - -### 3. 转换示例 -```python -# 元转分 (API输入 -> 数据库) -def yuan_to_fen(yuan: float) -> int: - return int(round(yuan * 100)) - -# 分转元 (数据库 -> API输出) -def fen_to_yuan(fen: int) -> float: - return round(fen / 100, 2) -``` - ---- - -## 🔐 幂等性设计 - -### 1. 订单创建幂等 -- 使用 `(user_id, product_id, created_date)` 组合判断 -- 或使用客户端传入的幂等键 `idempotency_key` - -### 2. 支付回调幂等 -- 检查交易状态,已支付则直接返回成功 -- 使用数据库事务 + 行锁保证并发安全 - -### 3. 退款幂等 -- 同一笔交易只能退款一次 (或限制总退款金额) diff --git a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md b/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md deleted file mode 100644 index 009567de..00000000 --- a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/安全与合规.md +++ /dev/null @@ -1,383 +0,0 @@ -# 支付安全与合规指南 (Security & Compliance) v4.0 - -> 支付系统安全最佳实践,保护你的资金和用户数据 - -## 🔐 密钥安全 - -### 1. 密钥存储原则 - -``` -❌ 错误做法: -- 将密钥硬编码在代码中 -- 将密钥提交到 Git 仓库 -- 通过即时通讯工具传输密钥 -- 使用弱密码作为 API Key - -✅ 正确做法: -- 使用环境变量存储密钥 -- 使用专业密钥管理服务 (AWS KMS, HashiCorp Vault) -- 定期轮换密钥 -- 最小权限原则 -``` - -### 2. .gitignore 必须包含 - -```gitignore -# 支付密钥相关 -.env -.env.local -.env.*.local -*.pem -*.key -cert/ -config/payment.yml -secrets/ -``` - -### 3. 密钥轮换 - -- 定期更换 API 密钥 (建议每 90 天) -- 发现泄露立即作废并重新生成 -- 保留旧密钥短暂过渡期 - ---- - -## 🔒 通信安全 - -### 1. HTTPS 强制 - -```nginx -# Nginx 配置示例 -server { - listen 80; - server_name your-domain.com; - return 301 https://$server_name$request_uri; -} - -server { - listen 443 ssl http2; - server_name your-domain.com; - - ssl_certificate /path/to/fullchain.pem; - ssl_certificate_key /path/to/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; - - # HSTS - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; -} -``` - -### 2. 证书管理 - -- 使用受信任的 CA 签发证书 -- 定期检查证书有效期 -- 推荐 Let's Encrypt 自动续期 - ---- - -## ✅ 签名验证 - -### 1. 支付宝签名验证 - -```python -from Crypto.PublicKey import RSA -from Crypto.Signature import PKCS1_v1_5 -from Crypto.Hash import SHA256 -import base64 - -def verify_alipay_sign(params: dict, sign: str, public_key: str) -> bool: - """验证支付宝签名""" - # 1. 参数排序 - sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign' and v]) - - # 2. 拼接待签名字符串 - sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params]) - - # 3. RSA2 验签 - key = RSA.import_key(f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----") - verifier = PKCS1_v1_5.new(key) - hash_obj = SHA256.new(sign_str.encode('utf-8')) - - try: - verifier.verify(hash_obj, base64.b64decode(sign)) - return True - except (ValueError, TypeError): - return False -``` - -### 2. 微信签名验证 - -```python -import hashlib - -def verify_wechat_sign(params: dict, sign: str, api_key: str) -> bool: - """验证微信支付签名""" - # 1. 参数排序 - sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign' and v]) - - # 2. 拼接待签名字符串 - sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params]) - sign_str += f'&key={api_key}' - - # 3. MD5 签名 - calculated_sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper() - - return calculated_sign == sign -``` - ---- - -## 💰 金额校验 - -### 1. 回调金额必须验证 - -```python -def handle_payment_notify(trade_sn: str, paid_amount: int): - """处理支付回调时必须验证金额""" - trade = get_trade_by_sn(trade_sn) - - # 金额必须严格匹配 - if paid_amount != trade.cash_amount: - log.error(f"金额不匹配! 订单:{trade.cash_amount}, 回调:{paid_amount}") - raise AmountMismatchError() - - # 继续处理... -``` - -### 2. 防止金额篡改 - -```python -# 前端传入的金额仅用于展示,实际金额从后端订单读取 -def checkout(order_sn: str, gateway: str): - order = get_order(order_sn) - - # 金额从数据库读取,不信任前端 - amount = order.pay_amount - - return create_trade(order_sn, amount, gateway) -``` - ---- - -## 🛡️ 回调安全 - -### 1. IP 白名单 - -```python -# 支付平台回调 IP 白名单 -PAYMENT_IP_WHITELIST = { - 'alipay': [ - '110.75.0.0/16', - '203.209.0.0/16' - ], - 'wechat': [ - '101.226.0.0/16', - '140.207.0.0/16' - ] -} - -def verify_callback_ip(gateway: str, client_ip: str) -> bool: - """验证回调来源 IP""" - import ipaddress - - whitelist = PAYMENT_IP_WHITELIST.get(gateway, []) - client = ipaddress.ip_address(client_ip) - - for cidr in whitelist: - if client in ipaddress.ip_network(cidr): - return True - - return False -``` - -### 2. 防重放攻击 - -```python -import time - -def check_notify_timestamp(timestamp: int) -> bool: - """检查回调时间戳,防止重放攻击""" - now = int(time.time()) - - # 允许 5 分钟的时间差 - if abs(now - timestamp) > 300: - log.warning(f"回调时间戳异常: {timestamp}") - return False - - return True -``` - -### 3. 幂等性处理 - -```python -def process_notify_idempotent(trade_sn: str, notify_data: dict): - """幂等性处理回调""" - - # 使用分布式锁 - lock_key = f"payment_notify:{trade_sn}" - - with redis_lock(lock_key, timeout=10): - trade = get_trade_by_sn(trade_sn) - - # 已处理过,直接返回成功 - if trade.status == 'paid': - return success_response() - - # 处理支付成功逻辑 - update_trade_to_paid(trade, notify_data) - - return success_response() -``` - ---- - -## 📝 日志审计 - -### 1. 必须记录的日志 - -```python -import logging - -payment_logger = logging.getLogger('payment') - -# 创建交易日志 -payment_logger.info(f"创建交易 | trade_sn={trade_sn} | order_sn={order_sn} | amount={amount} | gateway={gateway}") - -# 回调日志 -payment_logger.info(f"收到回调 | gateway={gateway} | trade_sn={trade_sn} | raw_data={raw_data[:500]}") - -# 签名验证日志 -payment_logger.info(f"签名验证 | trade_sn={trade_sn} | result={verify_result}") - -# 状态变更日志 -payment_logger.info(f"状态变更 | trade_sn={trade_sn} | from={old_status} | to={new_status}") - -# 退款日志 -payment_logger.info(f"发起退款 | refund_sn={refund_sn} | trade_sn={trade_sn} | amount={amount}") -``` - -### 2. 敏感信息脱敏 - -```python -def mask_sensitive(data: dict) -> dict: - """敏感信息脱敏""" - sensitive_keys = ['card_no', 'id_card', 'phone', 'bank_account'] - - masked = data.copy() - for key in sensitive_keys: - if key in masked: - value = str(masked[key]) - if len(value) > 4: - masked[key] = value[:2] + '*' * (len(value) - 4) + value[-2:] - - return masked -``` - ---- - -## 🚨 异常处理 - -### 1. 支付异常分类 - -```python -class PaymentError(Exception): - """支付基础异常""" - pass - -class SignatureError(PaymentError): - """签名验证失败""" - pass - -class AmountMismatchError(PaymentError): - """金额不匹配""" - pass - -class OrderExpiredError(PaymentError): - """订单已过期""" - pass - -class DuplicatePaymentError(PaymentError): - """重复支付""" - pass - -class RefundError(PaymentError): - """退款失败""" - pass -``` - -### 2. 统一异常处理 - -```python -@app.exception_handler(PaymentError) -async def payment_exception_handler(request, exc): - return JSONResponse( - status_code=400, - content={ - "code": 400, - "message": str(exc), - "data": None - } - ) -``` - ---- - -## ✅ 合规要求 - -### 1. PCI DSS 合规 (信用卡) - -- 不存储完整卡号、CVV、PIN -- 使用 Stripe/PayPal 等符合 PCI DSS 的支付网关 -- 定期安全评估 - -### 2. GDPR 合规 (欧盟用户) - -- 明确告知用户数据用途 -- 提供数据删除功能 -- 用户同意授权 - -### 3. 中国支付合规 - -- 接入持牌支付机构 -- 实名认证 -- 交易限额管理 - ---- - -## 📋 安全检查清单 - -```markdown -## 上线前安全检查 - -### 密钥管理 -- [ ] 所有密钥通过环境变量配置 -- [ ] 密钥未提交到代码仓库 -- [ ] 生产环境密钥与测试环境隔离 - -### 通信安全 -- [ ] 启用 HTTPS -- [ ] 证书有效且受信任 -- [ ] 启用 HSTS - -### 签名验证 -- [ ] 所有回调验签 -- [ ] 验签失败拒绝处理 - -### 金额校验 -- [ ] 回调金额与订单金额比对 -- [ ] 金额从后端读取 - -### 日志审计 -- [ ] 关键操作有日志 -- [ ] 敏感信息脱敏 - -### 异常处理 -- [ ] 异常不泄露敏感信息 -- [ ] 有统一异常处理 - -### 回调安全 -- [ ] IP 白名单验证 (可选) -- [ ] 幂等性处理 -- [ ] 防重放攻击 -``` diff --git a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml b/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml deleted file mode 100644 index 225a7af1..00000000 --- a/next-project/addons/Universal_Payment_Module copy/1_核心设计_通用协议/标准配置模板.yaml +++ /dev/null @@ -1,133 +0,0 @@ -# ============================================================================ -# 全球支付模块标准配置模板 (Universal Payment Config Template) v4.0 -# ============================================================================ -# 适用于: Python, Node.js, Go, Java, PHP 等任意后端语言 -# 使用方法: 将此配置映射到你项目的环境变量 (.env, config.py, application.yml) -# ============================================================================ - -# ---------------------------------------------------------------------------- -# 1. 基础环境 (Environment) -# ---------------------------------------------------------------------------- -APP_ENV: "production" # development / staging / production -APP_NAME: "MyApp" # 应用名称 (用于日志/标题) -APP_URL: "https://your-site.com" # 你的网站域名 (用于回调地址生成) -APP_CURRENCY: "CNY" # 默认货币: CNY, USD, EUR - -# ---------------------------------------------------------------------------- -# 2. 数据库 (Database) - 存储订单和交易流水 -# ---------------------------------------------------------------------------- -DB_CONNECTION: "mysql" # mysql / postgres / mongodb / sqlite -DB_HOST: "127.0.0.1" -DB_PORT: "3306" -DB_DATABASE: "payment_db" -DB_USERNAME: "root" -DB_PASSWORD: "your_password" - -# 自动创建的表: -# - orders (订单表) -# - pay_trades (交易流水表) -# - cashflows (资金流水表) - -# ---------------------------------------------------------------------------- -# 3. 支付宝 (Alipay) - 中国市场 -# ---------------------------------------------------------------------------- -ALIPAY_ENABLED: true -ALIPAY_MODE: "production" # sandbox / production -ALIPAY_APP_ID: "" # 开放平台应用 AppID -ALIPAY_PID: "" # 商户 PID (合作伙伴ID) -ALIPAY_SELLER_EMAIL: "" # 收款支付宝账号 -ALIPAY_PRIVATE_KEY: "" # 商户私钥 (RSA2) -ALIPAY_PUBLIC_KEY: "" # 支付宝公钥 -ALIPAY_MD5_KEY: "" # MD5 密钥 (旧版接口) - -# 回调地址 (系统自动拼接 APP_URL) -# 同步回调: ${APP_URL}/api/payment/return/alipay -# 异步回调: ${APP_URL}/api/payment/notify/alipay - -# ---------------------------------------------------------------------------- -# 4. 微信支付 (Wechat Pay) - 中国市场 -# ---------------------------------------------------------------------------- -WECHAT_ENABLED: true -WECHAT_MODE: "production" # sandbox / production - -# 公众号/网站支付 -WECHAT_APPID: "" # 公众号/网站 AppID -WECHAT_APP_SECRET: "" # AppSecret - -# 服务号 (如果有独立服务号) -WECHAT_SERVICE_APPID: "" # 服务号 AppID -WECHAT_SERVICE_SECRET: "" # 服务号 AppSecret - -# 商户信息 -WECHAT_MCH_ID: "" # 商户号 -WECHAT_MCH_KEY: "" # 商户平台 API 密钥 (32位) -WECHAT_MCH_KEY_V3: "" # APIv3 密钥 (如使用v3接口) - -# 证书路径 (相对于项目根目录) -WECHAT_CERT_PATH: "./cert/wechat/apiclient_cert.pem" -WECHAT_KEY_PATH: "./cert/wechat/apiclient_key.pem" -WECHAT_CERT_SERIAL: "" # 证书序列号 (v3接口需要) - -# 小程序 (如果有) -WECHAT_MINI_APPID: "" # 小程序 AppID -WECHAT_MINI_SECRET: "" # 小程序 AppSecret - -# 回调地址 -# 异步回调: ${APP_URL}/api/payment/notify/wechat - -# ---------------------------------------------------------------------------- -# 5. PayPal - 全球市场 -# ---------------------------------------------------------------------------- -PAYPAL_ENABLED: true -PAYPAL_MODE: "live" # sandbox / live -PAYPAL_CLIENT_ID: "" # Client ID -PAYPAL_CLIENT_SECRET: "" # Client Secret -PAYPAL_WEBHOOK_ID: "" # Webhook ID (用于验证回调) - -# 回调地址: ${APP_URL}/api/payment/notify/paypal - -# ---------------------------------------------------------------------------- -# 6. Stripe - 全球市场 -# ---------------------------------------------------------------------------- -STRIPE_ENABLED: true -STRIPE_MODE: "live" # test / live -STRIPE_PUBLIC_KEY: "" # pk_live_xxx 或 pk_test_xxx -STRIPE_SECRET_KEY: "" # sk_live_xxx 或 sk_test_xxx -STRIPE_WEBHOOK_SECRET: "" # whsec_xxx - -# 回调地址: ${APP_URL}/api/payment/notify/stripe - -# ---------------------------------------------------------------------------- -# 7. USDT (加密货币) - Web3 / 抗审查支付 -# ---------------------------------------------------------------------------- -USDT_ENABLED: false -USDT_GATEWAY_TYPE: "nowpayments" # nowpayments / native - -# 选项 A: NOWPayments (第三方托管) -NOWPAYMENTS_API_KEY: "" -NOWPAYMENTS_IPN_SECRET: "" - -# 选项 B: Native (原生 TRC20 监听) -TRON_NODE_API: "https://api.trongrid.io" -TRON_WALLET_ADDRESS: "" # 你的 USDT-TRC20 收款地址 -TRON_API_KEY: "" # TronGrid API Key -TRON_CHECK_INTERVAL: 60 # 轮询间隔 (秒) - -# ---------------------------------------------------------------------------- -# 8. 高级配置 (Advanced) -# ---------------------------------------------------------------------------- -# 虚拟币/积分系统 -COIN_ENABLED: false # 是否启用虚拟币抵扣 -COIN_RATE: 100 # 1元 = 100虚拟币 - -# 订单配置 -ORDER_EXPIRE_MINUTES: 30 # 订单过期时间 (分钟) -TRADE_SN_PREFIX: "T" # 交易流水号前缀 - -# 日志配置 -PAYMENT_LOG_LEVEL: "info" # debug / info / warning / error -PAYMENT_LOG_PATH: "./logs/payment.log" - -# 安全配置 -PAYMENT_IP_WHITELIST: "" # 回调IP白名单 (逗号分隔) -PAYMENT_SIGN_TYPE: "RSA2" # 签名类型: RSA2, MD5 diff --git a/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md b/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md deleted file mode 100644 index ad7d0ddc..00000000 --- a/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/Cursor规则.md +++ /dev/null @@ -1,266 +0,0 @@ -# Universal Payment Module - Cursor 规则 - -> 将此文件复制为项目根目录的 `.cursorrules` 或 `.cursor/rules/payment.md` - ---- - -## 基础设定 - -你是一位精通全球支付架构的资深全栈工程师,专注于支付网关集成、安全合规和高可用设计。 - -当用户提及"支付模块"、"支付功能"、"接入支付"时,请参考 `Universal_Payment_Module` 目录中的设计文档。 - ---- - -## 核心原则 - -### 1. 配置驱动 -- 所有支付密钥通过环境变量配置,绝不硬编码 -- 使用 `.env` 文件管理配置,参考 `标准配置模板.yaml` -- 支持多环境切换 (development/staging/production) - -### 2. 工厂模式 -- 使用 `PaymentFactory` 统一管理支付网关 -- 每个网关实现统一的 `AbstractGateway` 接口 -- 支持动态添加新的支付渠道 - -### 3. 安全优先 -- 所有回调必须验证签名 -- 必须验证支付金额与订单金额匹配 -- 使用 HTTPS,敏感数据脱敏 -- 参考 `安全与合规.md` - -### 4. 幂等性 -- 支付回调必须支持重复调用 -- 使用分布式锁防止并发问题 -- 检查交易状态后再处理 - -### 5. 状态机 -- 订单状态: created → paying → paid → refunded/closed -- 状态变更必须记录日志 -- 不允许跳跃式状态变更 - ---- - -## API 规范 - -严格按照 `API接口定义.md` 实现以下接口: - -``` -POST /api/payment/create_order - 创建订单 -POST /api/payment/checkout - 发起支付 -GET /api/payment/status/{sn} - 查询状态 -POST /api/payment/close/{sn} - 关闭订单 -POST /api/payment/notify/{gw} - 回调通知 -GET /api/payment/return/{gw} - 同步返回 -GET /api/payment/methods - 获取支付方式 -``` - -### 统一响应格式 - -```json -{ - "code": 200, - "message": "success", - "data": { ... } -} -``` - ---- - -## 数据库模型 - -参考 `业务逻辑与模型.md`,必须创建以下表: - -1. **orders** - 订单表 (业务订单) -2. **pay_trades** - 交易流水表 (支付记录) -3. **cashflows** - 资金流水表 (可选) -4. **refunds** - 退款记录表 - -金额单位: -- 数据库存储使用**分** (BIGINT) -- API 输入输出使用**元** (float) - ---- - -## 支付网关 Gateway - -### 支付宝 Alipay -- SDK: `alipay-sdk-python` / `alipay-sdk-java` / `alipay/aop-sdk` -- 签名: RSA2 -- 回调: POST,form 表单格式 - -### 微信支付 Wechat -- SDK: `wechatpay-python-v3` / `wechatpay-java` -- 签名: HMAC-SHA256 / RSA (v3) -- 回调: POST,XML 格式 - -### PayPal -- SDK: `paypal-rest-sdk` -- 认证: OAuth 2.0 -- 回调: Webhook,JSON 格式 - -### Stripe -- SDK: `stripe` -- 认证: API Key -- 回调: Webhook,JSON 格式 - ---- - -## 代码生成模板 - -### Python FastAPI - -```python -# app/services/payment_factory.py -from abc import ABC, abstractmethod -from app.config import settings - -class AbstractGateway(ABC): - @abstractmethod - async def create_trade(self, data: dict) -> dict: - pass - - @abstractmethod - def verify_sign(self, data: dict) -> bool: - pass - - @abstractmethod - def parse_notify(self, data: dict) -> dict: - pass - -class PaymentFactory: - _gateways = {} - - @classmethod - def register(cls, name: str, gateway_class): - cls._gateways[name] = gateway_class - - @classmethod - def create(cls, name: str) -> AbstractGateway: - gateway_name = name.split('_')[0] - if gateway_name not in cls._gateways: - raise ValueError(f"不支持的支付网关: {name}") - return cls._gateways[gateway_name]() -``` - -### Node.js Express - -```typescript -// src/services/PaymentFactory.ts -export abstract class AbstractGateway { - abstract createTrade(data: CreateTradeDTO): Promise; - abstract verifySign(data: Record): boolean; - abstract parseNotify(data: Record): NotifyResult; -} - -export class PaymentFactory { - private static gateways: Map AbstractGateway> = new Map(); - - static register(name: string, gateway: new () => AbstractGateway) { - this.gateways.set(name, gateway); - } - - static create(name: string): AbstractGateway { - const gatewayName = name.split('_')[0]; - const GatewayClass = this.gateways.get(gatewayName); - if (!GatewayClass) { - throw new Error(`不支持的支付网关: ${name}`); - } - return new GatewayClass(); - } -} -``` - ---- - -## 回调处理模板 - -```python -async def handle_notify(gateway: str, raw_data: bytes | dict): - """统一回调处理""" - - # 1. 获取网关驱动 - driver = PaymentFactory.create(gateway) - - # 2. 验证签名 - if not driver.verify_sign(raw_data): - logger.error(f"签名验证失败 | gateway={gateway}") - raise SignatureError() - - # 3. 解析数据 - parsed = driver.parse_notify(raw_data) - trade_sn = parsed['trade_sn'] - - # 4. 幂等检查 - async with redis_lock(f"notify:{trade_sn}"): - trade = await get_trade(trade_sn) - - if trade.status == 'paid': - return driver.success_response() - - # 5. 金额校验 - if parsed['amount'] != trade.cash_amount: - logger.error(f"金额不匹配 | sn={trade_sn}") - raise AmountMismatchError() - - # 6. 更新状态 - await update_trade_status(trade_sn, 'paid', parsed) - - # 7. 触发业务回调 - await dispatch_event('order.paid', trade.order_sn) - - return driver.success_response() -``` - ---- - -## 日志规范 - -关键操作必须记录日志: - -```python -logger.info(f"创建交易 | trade_sn={sn} | order={order_sn} | amount={amount}") -logger.info(f"收到回调 | gateway={gw} | trade_sn={sn}") -logger.info(f"签名验证 | trade_sn={sn} | result={ok}") -logger.info(f"状态变更 | trade_sn={sn} | {old} → {new}") -logger.error(f"支付失败 | trade_sn={sn} | error={err}") -``` - ---- - -## 测试规范 - -1. **单元测试**: 测试签名生成/验证、金额转换 -2. **集成测试**: 使用沙箱环境测试完整支付流程 -3. **Mock 测试**: 模拟回调接口测试 - ---- - -## 禁止事项 - -❌ 密钥硬编码在代码中 -❌ 跳过签名验证 -❌ 信任前端传入的金额 -❌ 回调不做幂等处理 -❌ 敏感信息打印到日志 -❌ 使用 HTTP 而非 HTTPS - ---- - -## 参考文档路径 - -``` -Universal_Payment_Module/ -├── 1_核心设计_通用协议/ -│ ├── 标准配置模板.yaml # 配置参考 -│ ├── API接口定义.md # 接口规范 -│ ├── 业务逻辑与模型.md # 数据模型 -│ └── 安全与合规.md # 安全规范 -├── 2_智能对接_AI指令/ -│ ├── 通用集成指令.md # AI 提示词 -│ └── Cursor规则.md # 本文件 -└── 3_逻辑参考_通用实现/ - ├── 前端收银台Demo.html - └── 后端源码/ -``` diff --git a/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md b/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md deleted file mode 100644 index a886e178..00000000 --- a/next-project/addons/Universal_Payment_Module copy/2_智能对接_AI指令/通用集成指令.md +++ /dev/null @@ -1,234 +0,0 @@ -# 通用支付模块 AI 智能对接指令 (Integration Prompt) v4.0 - -> 发送此指令给 AI 助手 (Cursor/ChatGPT/Claude),自动生成支付集成代码 - ---- - -## 🎯 角色设定 - -``` -你是一位精通全球支付架构的资深全栈架构师,专注于: -- 支付网关集成 (Alipay/Wechat/PayPal/Stripe/USDT) -- 安全合规 (签名验证/HTTPS/PCI DSS) -- 高可用设计 (幂等性/状态机/分布式锁) -``` - ---- - -## 📋 任务目标 - -我提供了一个**配置驱动 (Configuration-Driven)** 的通用支付模块设计。 -请根据我的项目环境,将此支付功能无缝集成。 - ---- - -## 📚 核心资源 (请先阅读) - -1. **标准配置模板**: `1_核心设计_通用协议/标准配置模板.yaml` -2. **API 接口契约**: `1_核心设计_通用协议/API接口定义.md` -3. **数据模型**: `1_核心设计_通用协议/业务逻辑与模型.md` -4. **安全规范**: `1_核心设计_通用协议/安全与合规.md` - ---- - -## 🔧 集成模式 - -### 模式 A: 嵌入式集成 (Library Mode) ⭐推荐 - -适用于将支付功能直接写在现有的后端项目中。 - -**执行步骤**: -1. **环境识别**: 检查项目语言 (Python/Node/Go/Java/PHP) -2. **依赖安装**: 推荐 SDK -3. **配置加载**: 读取环境变量 -4. **模型生成**: 创建 ORM 模型 (Order/Trade/Refund) -5. **网关工厂**: 实现 PaymentFactory + 各网关 Driver -6. **接口实现**: 按 `API接口定义.md` 实现 Controller -7. **回调处理**: 实现回调验签和状态更新 - ---- - -### 模式 B: 微服务集成 (Microservice Mode) - -适用于将支付功能独立部署为一个服务。 - -**执行步骤**: -1. **服务生成**: 创建独立的支付服务项目 -2. **Docker化**: 编写 `Dockerfile` 和 `docker-compose.yml` -3. **网关代理**: 配置 `/api/payment/*` 路由转发 - ---- - -## 📝 给 AI 的标准执行指令 - -### 快速集成 (复制此内容发送给 AI) - -``` -请读取 `Universal_Payment_Module` 目录下的所有设计文档。 - -我的当前项目信息: -- 语言/框架: [Python FastAPI / Node.js Express / Java Spring Boot / Go Gin / PHP Laravel] -- 数据库: [MySQL / PostgreSQL / MongoDB] -- 集成模式: [模式 A 嵌入式 / 模式 B 微服务] - -请执行以下任务: -1. 生成依赖安装命令 -2. 生成数据库迁移/模型代码 -3. 生成支付网关工厂类 -4. 生成 API 接口代码 (严格按 API接口定义.md) -5. 生成回调处理代码 -6. 生成配置读取代码 - -要求: -- 使用工厂模式管理支付网关 -- 所有配置通过环境变量读取 -- 包含完整的签名验证逻辑 -- 包含幂等性处理 -- 添加中文注释 -``` - ---- - -## 🐍 Python FastAPI 示例指令 - -``` -我的项目使用 Python FastAPI + SQLAlchemy + MySQL。 - -请根据 Universal_Payment_Module 文档,生成: - -1. requirements.txt 依赖 -2. app/models/payment.py - 数据模型 -3. app/services/payment_factory.py - 支付网关工厂 -4. app/services/gateways/alipay.py - 支付宝网关 -5. app/services/gateways/wechat.py - 微信支付网关 -6. app/routers/payment.py - API 路由 -7. app/config/payment.py - 配置加载 - -特别要求: -- 使用 async/await 异步处理 -- 集成 alipay-sdk-python 和 wechatpay-python-v3 -- 回调接口支持 XML 和 JSON 格式 -``` - ---- - -## 🟢 Node.js Express 示例指令 - -``` -我的项目使用 Node.js Express + Prisma + PostgreSQL。 - -请根据 Universal_Payment_Module 文档,生成: - -1. package.json 依赖 -2. prisma/schema.prisma - 数据模型 -3. src/services/PaymentFactory.ts - 支付网关工厂 -4. src/services/gateways/AlipayGateway.ts -5. src/services/gateways/WechatGateway.ts -6. src/routes/payment.ts - API 路由 -7. src/config/payment.ts - 配置加载 - -特别要求: -- 使用 TypeScript -- 使用 alipay-sdk 和 wechatpay-node-v3 -- 实现完整的错误处理 -``` - ---- - -## ☕ Java Spring Boot 示例指令 - -``` -我的项目使用 Java Spring Boot + MyBatis + MySQL。 - -请根据 Universal_Payment_Module 文档,生成: - -1. pom.xml 依赖 -2. entity/ - 实体类 -3. mapper/ - MyBatis Mapper -4. service/PaymentFactory.java - 支付网关工厂 -5. service/gateway/AlipayGateway.java -6. service/gateway/WechatGateway.java -7. controller/PaymentController.java - API 控制器 -8. config/PaymentConfig.java - 配置类 - -特别要求: -- 使用 alipay-sdk-java 和 wechatpay-java -- 使用 @Transactional 事务管理 -- 实现统一异常处理 -``` - ---- - -## 🐘 PHP Laravel 示例指令 - -``` -我的项目使用 PHP Laravel + Eloquent + MySQL。 - -请根据 Universal_Payment_Module 文档,生成: - -1. composer.json 依赖 -2. database/migrations/ - 数据库迁移 -3. app/Models/ - Eloquent 模型 -4. app/Services/PaymentFactory.php - 支付网关工厂 -5. app/Services/Gateways/AlipayGateway.php -6. app/Services/Gateways/WechatGateway.php -7. app/Http/Controllers/PaymentController.php -8. routes/api.php - 路由定义 -9. config/payment.php - 配置文件 - -特别要求: -- 使用 alipay/aop-sdk 和 wechatpay/wechatpay -- 使用 Laravel 的服务容器 -- 实现中间件验签 -``` - ---- - -## 🔥 高级指令:前端收银台 - -``` -请根据 Universal_Payment_Module 文档,生成前端收银台组件。 - -技术栈: [Vue 3 / React / 原生 JS] - -要求: -1. 支持多种支付方式切换 -2. 扫码支付显示二维码 -3. 轮询支付状态 -4. 适配移动端 -5. 显示支付倒计时 -6. 美观的 UI (可使用 TailwindCSS) -``` - ---- - -## 🔥 高级指令:Docker 部署 - -``` -请为 Universal_Payment_Module 生成 Docker 部署配置。 - -要求: -1. Dockerfile (多阶段构建) -2. docker-compose.yml (包含 MySQL + Redis) -3. nginx.conf (反向代理 + HTTPS) -4. .env.example (环境变量模板) -5. deploy.sh (一键部署脚本) -``` - ---- - -## ⚠️ 注意事项 - -1. **密钥安全**: 生成的代码中不要硬编码任何密钥 -2. **签名验证**: 必须实现完整的签名验证逻辑 -3. **幂等处理**: 回调必须支持幂等 -4. **金额校验**: 必须验证回调金额与订单金额匹配 -5. **日志记录**: 关键操作必须记录日志 - ---- - -## 📞 支持 - -如有问题,请联系: -- 微信: 28533368 -- 作者: 卡若 diff --git a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html b/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html deleted file mode 100644 index 060af045..00000000 --- a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html +++ /dev/null @@ -1,748 +0,0 @@ - - - - - - 通用收银台 v4.0 - - - - -
- -
-
-
VIP会员月卡
-
- ¥ - 99.00 -
-
- 订单号: 202401170001 - 29:59 -
-
- - -
-

选择支付方式

-
- -
-
💙
-
-
支付宝
-
推荐有支付宝账户的用户使用
-
-
-
- - -
-
💚
-
-
微信支付
-
扫码支付,微信用户首选
-
-
-
- - -
-
🅿️
-
-
PayPal
-
支持信用卡,海外用户推荐
-
-
-
- - -
-
-
-
USDT (TRC20)
-
加密货币支付
-
-
-
-
-
- - -
-
- 支付二维码 -
-
请使用微信扫描二维码支付
-
二维码有效期 30 分钟,请尽快支付
- - - - -
- - - -
- - -
-
-

支付成功

-

感谢您的购买,订单已完成

- -
- - -
- 🔒 安全支付由卡若私域提供技术支持 -
-
- - - - - diff --git a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py b/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py deleted file mode 100644 index e17e637d..00000000 --- a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/alipay_gateway.py +++ /dev/null @@ -1,294 +0,0 @@ -""" -支付宝网关实现 (Alipay Gateway) -基于 www.lytiao.com 项目提取 - -作者: 卡若 -版本: v4.0 -""" - -import json -import base64 -import hashlib -import logging -from typing import Dict, Any, Optional -from urllib.parse import urlencode, parse_qs - -try: - from Crypto.PublicKey import RSA - from Crypto.Signature import PKCS1_v1_5 - from Crypto.Hash import SHA256 -except ImportError: - print("请安装 pycryptodome: pip install pycryptodome") - -from payment_factory import ( - AbstractGateway, CreateTradeData, TradeResult, NotifyResult, - SignatureError, logger -) - - -class AlipayGateway(AbstractGateway): - """ - 支付宝网关 - - 支持: - - 电脑网站支付 (platform_type='web') - - 手机网站支付 (platform_type='wap') - - 扫码支付 (platform_type='qr') - """ - - GATEWAY_URL = 'https://openapi.alipay.com/gateway.do' - SANDBOX_URL = 'https://openapi.alipaydev.com/gateway.do' - - def __init__(self, config: Dict[str, Any]): - super().__init__(config) - self.app_id = config.get('app_id', '') - self.pid = config.get('pid', '') - self.seller_email = config.get('seller_email', '') - self.private_key = config.get('private_key', '') - self.public_key = config.get('public_key', '') - self.md5_key = config.get('md5_key', '') - - def create_trade(self, data: CreateTradeData) -> TradeResult: - """创建支付宝交易""" - platform_type = data.platform_type.capitalize() - - if platform_type == 'Web': - return self._create_web_trade(data) - elif platform_type == 'Wap': - return self._create_wap_trade(data) - elif platform_type == 'Qr': - return self._create_qr_trade(data) - else: - raise ValueError(f"不支持的支付类型: {platform_type}") - - def _create_web_trade(self, data: CreateTradeData) -> TradeResult: - """电脑网站支付""" - biz_content = { - 'subject': data.goods_title[:256], - 'out_trade_no': data.trade_sn, - 'total_amount': str(data.amount / 100), # 分转元 - 'product_code': 'FAST_INSTANT_TRADE_PAY', - 'body': data.goods_detail[:128] if data.goods_detail else '', - 'passback_params': json.dumps(data.attach) if data.attach else '', - } - - params = self._build_params('alipay.trade.page.pay', biz_content, data.return_url, data.notify_url) - - # 生成签名 - sign = self._generate_sign(params) - params['sign'] = sign - - # 构建跳转URL - pay_url = f"{self.GATEWAY_URL}?{urlencode(params)}" - - return TradeResult( - type='url', - payload=pay_url, - trade_sn=data.trade_sn - ) - - def _create_wap_trade(self, data: CreateTradeData) -> TradeResult: - """手机网站支付""" - biz_content = { - 'subject': data.goods_title[:256], - 'out_trade_no': data.trade_sn, - 'total_amount': str(data.amount / 100), - 'product_code': 'QUICK_WAP_WAY', - 'body': data.goods_detail[:128] if data.goods_detail else '', - 'passback_params': json.dumps(data.attach) if data.attach else '', - } - - params = self._build_params('alipay.trade.wap.pay', biz_content, data.return_url, data.notify_url) - sign = self._generate_sign(params) - params['sign'] = sign - - pay_url = f"{self.GATEWAY_URL}?{urlencode(params)}" - - return TradeResult( - type='url', - payload=pay_url, - trade_sn=data.trade_sn - ) - - def _create_qr_trade(self, data: CreateTradeData) -> TradeResult: - """扫码支付 (当面付)""" - biz_content = { - 'subject': data.goods_title[:256], - 'out_trade_no': data.trade_sn, - 'total_amount': str(data.amount / 100), - 'body': data.goods_detail[:128] if data.goods_detail else '', - } - - params = self._build_params('alipay.trade.precreate', biz_content, '', data.notify_url) - sign = self._generate_sign(params) - params['sign'] = sign - - # 调用接口获取二维码 - import requests - response = requests.post(self.GATEWAY_URL, data=params) - result = response.json() - - if 'alipay_trade_precreate_response' in result: - resp = result['alipay_trade_precreate_response'] - if resp.get('code') == '10000': - return TradeResult( - type='qrcode', - payload=resp['qr_code'], - trade_sn=data.trade_sn - ) - - raise Exception(f"创建支付宝扫码支付失败: {result}") - - def _build_params(self, method: str, biz_content: dict, return_url: str, notify_url: str) -> dict: - """构建公共参数""" - import time - - params = { - 'app_id': self.app_id, - 'method': method, - 'charset': 'utf-8', - 'sign_type': 'RSA2', - 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), - 'version': '1.0', - 'biz_content': json.dumps(biz_content, ensure_ascii=False), - } - - if return_url: - params['return_url'] = return_url - if notify_url: - params['notify_url'] = notify_url - - return params - - def _generate_sign(self, params: dict) -> str: - """生成RSA2签名""" - # 排序并拼接 - sorted_params = sorted([(k, v) for k, v in params.items() if v]) - sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params]) - - # RSA2签名 - key = RSA.import_key(f"-----BEGIN RSA PRIVATE KEY-----\n{self.private_key}\n-----END RSA PRIVATE KEY-----") - signer = PKCS1_v1_5.new(key) - hash_obj = SHA256.new(sign_str.encode('utf-8')) - signature = signer.sign(hash_obj) - - return base64.b64encode(signature).decode('utf-8') - - def verify_sign(self, data: Dict[str, Any]) -> bool: - """验证支付宝签名""" - sign = data.pop('sign', '') - sign_type = data.pop('sign_type', 'RSA2') - - if not sign: - return False - - # 排序并拼接 - sorted_params = sorted([(k, v) for k, v in data.items() if v and k != 'sign_type']) - sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params]) - - try: - key = RSA.import_key(f"-----BEGIN PUBLIC KEY-----\n{self.public_key}\n-----END PUBLIC KEY-----") - verifier = PKCS1_v1_5.new(key) - hash_obj = SHA256.new(sign_str.encode('utf-8')) - verifier.verify(hash_obj, base64.b64decode(sign)) - return True - except (ValueError, TypeError) as e: - logger.error(f"支付宝签名验证失败: {e}") - return False - - def parse_notify(self, data: Dict[str, Any]) -> NotifyResult: - """解析支付宝回调""" - trade_status = data.get('trade_status', '') - - if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']: - status = 'paid' - else: - status = 'failed' - - # 解析透传参数 - attach = {} - passback = data.get('passback_params', '') - if passback: - try: - attach = json.loads(passback) - except: - pass - - # 解析支付时间 - import time - gmt_payment = data.get('gmt_payment', '') - if gmt_payment: - pay_time = int(time.mktime(time.strptime(gmt_payment, '%Y-%m-%d %H:%M:%S'))) - else: - pay_time = int(time.time()) - - return NotifyResult( - status=status, - trade_sn=data.get('out_trade_no', ''), - platform_sn=data.get('trade_no', ''), - pay_amount=int(float(data.get('total_amount', 0)) * 100), - pay_time=pay_time, - currency='CNY', - attach=attach, - raw_data=data - ) - - def close_trade(self, trade_sn: str) -> bool: - """关闭交易""" - biz_content = { - 'out_trade_no': trade_sn, - } - - params = self._build_params('alipay.trade.close', biz_content, '', '') - sign = self._generate_sign(params) - params['sign'] = sign - - import requests - response = requests.post(self.GATEWAY_URL, data=params) - result = response.json() - - resp = result.get('alipay_trade_close_response', {}) - return resp.get('code') == '10000' - - def query_trade(self, trade_sn: str) -> Optional[NotifyResult]: - """查询交易""" - biz_content = { - 'out_trade_no': trade_sn, - } - - params = self._build_params('alipay.trade.query', biz_content, '', '') - sign = self._generate_sign(params) - params['sign'] = sign - - import requests - response = requests.post(self.GATEWAY_URL, data=params) - result = response.json() - - resp = result.get('alipay_trade_query_response', {}) - if resp.get('code') == '10000' and resp.get('trade_status') in ['TRADE_SUCCESS', 'TRADE_FINISHED']: - return self.parse_notify(resp) - - return None - - def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool: - """发起退款""" - biz_content = { - 'out_trade_no': trade_sn, - 'out_request_no': refund_sn, - 'refund_amount': str(amount / 100), - 'refund_reason': reason or '用户申请退款', - } - - params = self._build_params('alipay.trade.refund', biz_content, '', '') - sign = self._generate_sign(params) - params['sign'] = sign - - import requests - response = requests.post(self.GATEWAY_URL, data=params) - result = response.json() - - resp = result.get('alipay_trade_refund_response', {}) - return resp.get('code') == '10000' - - def success_response(self) -> str: - return 'success' diff --git a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py b/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py deleted file mode 100644 index bc1791bd..00000000 --- a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/payment_factory.py +++ /dev/null @@ -1,339 +0,0 @@ -""" -支付网关工厂 (Payment Gateway Factory) -基于 www.lytiao.com 项目提取的支付逻辑,适配 Python FastAPI - -作者: 卡若 -版本: v4.0 -""" - -import os -import time -import hashlib -import logging -from abc import ABC, abstractmethod -from typing import Dict, Any, Optional, Tuple -from dataclasses import dataclass -from enum import Enum - -logger = logging.getLogger('payment') - - -class PaymentPlatform(Enum): - """支付平台枚举""" - ALIPAY = 'alipay' - WECHAT = 'wechat' - PAYPAL = 'paypal' - STRIPE = 'stripe' - USDT = 'usdt' - COIN = 'coin' - - -class TradeType(Enum): - """交易类型""" - PURCHASE = 'purchase' # 购买 - RECHARGE = 'recharge' # 充值 - - -class TradeStatus(Enum): - """交易状态""" - PAYING = 'paying' - PAID = 'paid' - CLOSED = 'closed' - REFUNDED = 'refunded' - - -@dataclass -class CreateTradeData: - """创建交易请求数据""" - goods_title: str - goods_detail: str - trade_sn: str - order_sn: str - amount: int # 金额,单位:分 - notify_url: str - return_url: str = '' - platform_type: str = 'web' # web/wap/jsapi/native/h5/app - create_ip: str = '' - open_id: str = '' # 微信JSAPI支付需要 - attach: Dict[str, Any] = None - - -@dataclass -class TradeResult: - """交易结果""" - type: str # url/qrcode/json/address/direct - payload: Any # 支付数据 - trade_sn: str - expiration: int = 1800 # 过期时间(秒) - - -@dataclass -class NotifyResult: - """回调解析结果""" - status: str - trade_sn: str - platform_sn: str - pay_amount: int # 分 - pay_time: int # 时间戳 - currency: str = 'CNY' - attach: Dict[str, Any] = None - raw_data: Dict[str, Any] = None - - -class PaymentException(Exception): - """支付异常基类""" - pass - - -class SignatureError(PaymentException): - """签名验证失败""" - pass - - -class AmountMismatchError(PaymentException): - """金额不匹配""" - pass - - -class GatewayNotFoundError(PaymentException): - """支付网关不存在""" - pass - - -class AbstractGateway(ABC): - """ - 支付网关抽象基类 - 所有支付网关必须实现此接口 - """ - - def __init__(self, config: Dict[str, Any] = None): - self.config = config or {} - - @abstractmethod - def create_trade(self, data: CreateTradeData) -> TradeResult: - """ - 创建交易 - - Args: - data: 交易数据 - - Returns: - TradeResult: 包含支付参数的结果 - """ - pass - - @abstractmethod - def verify_sign(self, data: Dict[str, Any]) -> bool: - """ - 验证签名 - - Args: - data: 回调原始数据 - - Returns: - bool: 签名是否有效 - """ - pass - - @abstractmethod - def parse_notify(self, data: Any) -> NotifyResult: - """ - 解析回调数据 - - Args: - data: 回调原始数据 (可能是dict或xml字符串) - - Returns: - NotifyResult: 解析后的结果 - """ - pass - - @abstractmethod - def close_trade(self, trade_sn: str) -> bool: - """ - 关闭交易 - - Args: - trade_sn: 交易流水号 - - Returns: - bool: 是否关闭成功 - """ - pass - - @abstractmethod - def query_trade(self, trade_sn: str) -> Optional[NotifyResult]: - """ - 查询交易状态 - - Args: - trade_sn: 交易流水号 - - Returns: - NotifyResult: 交易状态,未支付返回None - """ - pass - - @abstractmethod - def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool: - """ - 发起退款 - - Args: - trade_sn: 原交易流水号 - refund_sn: 退款单号 - amount: 退款金额(分) - reason: 退款原因 - - Returns: - bool: 是否成功 - """ - pass - - def success_response(self) -> str: - """回调成功响应""" - return 'success' - - def fail_response(self) -> str: - """回调失败响应""" - return 'fail' - - -class PaymentFactory: - """ - 支付网关工厂 - - 使用示例: - # 注册网关 - PaymentFactory.register('alipay', AlipayGateway) - PaymentFactory.register('wechat', WechatGateway) - - # 创建网关实例 - gateway = PaymentFactory.create('wechat_jsapi') - result = gateway.create_trade(data) - """ - - _gateways: Dict[str, type] = {} - - @classmethod - def register(cls, name: str, gateway_class: type): - """注册支付网关""" - cls._gateways[name] = gateway_class - logger.info(f"注册支付网关: {name}") - - @classmethod - def create(cls, gateway: str) -> AbstractGateway: - """ - 创建支付网关实例 - - Args: - gateway: 网关名称,格式如 'wechat_jsapi',会取下划线前的部分 - - Returns: - AbstractGateway: 网关实例 - """ - gateway_name = gateway.split('_')[0] - - if gateway_name not in cls._gateways: - raise GatewayNotFoundError(f"不支持的支付网关: {gateway}") - - gateway_class = cls._gateways[gateway_name] - config = cls._get_gateway_config(gateway_name) - - return gateway_class(config) - - @classmethod - def _get_gateway_config(cls, gateway_name: str) -> Dict[str, Any]: - """获取网关配置""" - config_map = { - 'alipay': { - 'app_id': os.getenv('ALIPAY_APP_ID', ''), - 'pid': os.getenv('ALIPAY_PID', ''), - 'seller_email': os.getenv('ALIPAY_SELLER_EMAIL', ''), - 'private_key': os.getenv('ALIPAY_PRIVATE_KEY', ''), - 'public_key': os.getenv('ALIPAY_PUBLIC_KEY', ''), - 'md5_key': os.getenv('ALIPAY_MD5_KEY', ''), - }, - 'wechat': { - 'appid': os.getenv('WECHAT_APPID', ''), - 'app_secret': os.getenv('WECHAT_APP_SECRET', ''), - 'mch_id': os.getenv('WECHAT_MCH_ID', ''), - 'mch_key': os.getenv('WECHAT_MCH_KEY', ''), - 'cert_path': os.getenv('WECHAT_CERT_PATH', ''), - 'key_path': os.getenv('WECHAT_KEY_PATH', ''), - }, - 'paypal': { - 'client_id': os.getenv('PAYPAL_CLIENT_ID', ''), - 'client_secret': os.getenv('PAYPAL_CLIENT_SECRET', ''), - 'mode': os.getenv('PAYPAL_MODE', 'sandbox'), - }, - 'stripe': { - 'public_key': os.getenv('STRIPE_PUBLIC_KEY', ''), - 'secret_key': os.getenv('STRIPE_SECRET_KEY', ''), - 'webhook_secret': os.getenv('STRIPE_WEBHOOK_SECRET', ''), - }, - } - - return config_map.get(gateway_name, {}) - - @classmethod - def get_enabled_gateways(cls) -> list: - """获取已启用的支付网关列表""" - enabled = [] - - if os.getenv('ALIPAY_ENABLED', 'false').lower() == 'true': - enabled.append({ - 'gateway': 'alipay', - 'name': '支付宝', - 'icon': '/icons/alipay.png', - 'types': ['web', 'wap', 'qr'] - }) - - if os.getenv('WECHAT_ENABLED', 'false').lower() == 'true': - enabled.append({ - 'gateway': 'wechat', - 'name': '微信支付', - 'icon': '/icons/wechat.png', - 'types': ['native', 'jsapi', 'h5', 'app'] - }) - - if os.getenv('PAYPAL_ENABLED', 'false').lower() == 'true': - enabled.append({ - 'gateway': 'paypal', - 'name': 'PayPal', - 'icon': '/icons/paypal.png', - 'types': ['redirect'] - }) - - if os.getenv('STRIPE_ENABLED', 'false').lower() == 'true': - enabled.append({ - 'gateway': 'stripe', - 'name': 'Stripe', - 'icon': '/icons/stripe.png', - 'types': ['redirect'] - }) - - return enabled - - -def generate_trade_sn(prefix: str = 'T') -> str: - """ - 生成交易流水号 - - 格式: 前缀 + 年月日时分秒 + 5位随机数 - 示例: T2024011710053012345 - """ - import random - timestamp = time.strftime('%Y%m%d%H%M%S') - random_num = random.randint(10000, 99999) - return f"{prefix}{timestamp}{random_num}" - - -def yuan_to_fen(yuan: float) -> int: - """元转分""" - return int(round(yuan * 100)) - - -def fen_to_yuan(fen: int) -> float: - """分转元""" - return round(fen / 100, 2) diff --git a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py b/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py deleted file mode 100644 index b6d09eb9..00000000 --- a/next-project/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/后端源码/python/wechat_gateway.py +++ /dev/null @@ -1,317 +0,0 @@ -""" -微信支付网关实现 (Wechat Pay Gateway) -基于 www.lytiao.com 项目提取 - -作者: 卡若 -版本: v4.0 -""" - -import json -import time -import hashlib -import logging -import xml.etree.ElementTree as ET -from typing import Dict, Any, Optional -from urllib.parse import urlencode -import uuid - -from payment_factory import ( - AbstractGateway, CreateTradeData, TradeResult, NotifyResult, - SignatureError, logger -) - - -class WechatGateway(AbstractGateway): - """ - 微信支付网关 - - 支持: - - Native扫码支付 (platform_type='native') - - JSAPI公众号/小程序支付 (platform_type='jsapi') - - H5支付 (platform_type='h5') - - APP支付 (platform_type='app') - """ - - UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder' - ORDER_QUERY_URL = 'https://api.mch.weixin.qq.com/pay/orderquery' - CLOSE_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/closeorder' - REFUND_URL = 'https://api.mch.weixin.qq.com/secapi/pay/refund' - - def __init__(self, config: Dict[str, Any]): - super().__init__(config) - self.appid = config.get('appid', '') - self.app_secret = config.get('app_secret', '') - self.mch_id = config.get('mch_id', '') - self.mch_key = config.get('mch_key', '') - self.cert_path = config.get('cert_path', '') - self.key_path = config.get('key_path', '') - - def create_trade(self, data: CreateTradeData) -> TradeResult: - """创建微信支付交易""" - platform_type = data.platform_type.upper() - - # 构建统一下单参数 - params = { - 'appid': self.appid, - 'mch_id': self.mch_id, - 'nonce_str': self._generate_nonce(), - 'body': data.goods_title[:128], - 'out_trade_no': data.trade_sn, - 'total_fee': str(data.amount), # 微信以分为单位 - 'spbill_create_ip': data.create_ip or '127.0.0.1', - 'notify_url': data.notify_url, - 'trade_type': platform_type, - 'attach': json.dumps(data.attach) if data.attach else '', - } - - # JSAPI需要openid - if platform_type == 'JSAPI': - if not data.open_id: - raise ValueError("微信JSAPI支付需要提供 openid") - params['openid'] = data.open_id - - # H5支付需要scene_info - if platform_type == 'MWEB': - params['scene_info'] = json.dumps({ - 'h5_info': { - 'type': 'Wap', - 'wap_url': data.return_url, - 'wap_name': data.goods_title[:32] - } - }) - - # 生成签名 - params['sign'] = self._generate_sign(params) - - # 调用统一下单接口 - import requests - xml_data = self._dict_to_xml(params) - response = requests.post(self.UNIFIED_ORDER_URL, data=xml_data.encode('utf-8')) - result = self._xml_to_dict(response.text) - - if result.get('return_code') != 'SUCCESS': - raise Exception(f"微信支付统一下单失败: {result.get('return_msg')}") - - if result.get('result_code') != 'SUCCESS': - raise Exception(f"微信支付统一下单失败: {result.get('err_code_des')}") - - # 根据类型返回不同格式 - if platform_type == 'NATIVE': - # 扫码支付返回二维码链接 - return TradeResult( - type='qrcode', - payload=result['code_url'], - trade_sn=data.trade_sn - ) - - elif platform_type == 'JSAPI': - # 公众号支付返回JS SDK参数 - prepay_id = result['prepay_id'] - js_params = { - 'appId': self.appid, - 'timeStamp': str(int(time.time())), - 'nonceStr': self._generate_nonce(), - 'package': f'prepay_id={prepay_id}', - 'signType': 'MD5', - } - js_params['paySign'] = self._generate_sign(js_params) - - return TradeResult( - type='json', - payload=js_params, - trade_sn=data.trade_sn - ) - - elif platform_type == 'MWEB': - # H5支付返回跳转链接 - mweb_url = result['mweb_url'] - if data.return_url: - mweb_url += f"&redirect_url={urlencode({'': data.return_url})[1:]}" - - return TradeResult( - type='url', - payload=mweb_url, - trade_sn=data.trade_sn - ) - - elif platform_type == 'APP': - # APP支付返回SDK参数 - prepay_id = result['prepay_id'] - app_params = { - 'appid': self.appid, - 'partnerid': self.mch_id, - 'prepayid': prepay_id, - 'package': 'Sign=WXPay', - 'noncestr': self._generate_nonce(), - 'timestamp': str(int(time.time())), - } - app_params['sign'] = self._generate_sign(app_params) - - return TradeResult( - type='json', - payload=app_params, - trade_sn=data.trade_sn - ) - - else: - raise ValueError(f"不支持的微信支付类型: {platform_type}") - - def verify_sign(self, data: Dict[str, Any]) -> bool: - """验证微信签名""" - sign = data.pop('sign', '') - if not sign: - return False - - calculated_sign = self._generate_sign(data) - return calculated_sign == sign - - def parse_notify(self, data: Any) -> NotifyResult: - """解析微信回调""" - # 如果是XML字符串,先转换为dict - if isinstance(data, str): - data = self._xml_to_dict(data) - - # 验证签名 - if not self.verify_sign(data.copy()): - raise SignatureError("微信签名验证失败") - - result_code = data.get('result_code', '') - - if result_code == 'SUCCESS': - status = 'paid' - else: - status = 'failed' - - # 解析透传参数 - attach = {} - attach_str = data.get('attach', '') - if attach_str: - try: - attach = json.loads(attach_str) - except: - pass - - # 解析支付时间 (格式: 20240117100530) - time_end = data.get('time_end', '') - if time_end: - pay_time = int(time.mktime(time.strptime(time_end, '%Y%m%d%H%M%S'))) - else: - pay_time = int(time.time()) - - return NotifyResult( - status=status, - trade_sn=data.get('out_trade_no', ''), - platform_sn=data.get('transaction_id', ''), - pay_amount=int(data.get('cash_fee', 0)), - pay_time=pay_time, - currency=data.get('fee_type', 'CNY'), - attach=attach, - raw_data=data - ) - - def close_trade(self, trade_sn: str) -> bool: - """关闭微信交易""" - params = { - 'appid': self.appid, - 'mch_id': self.mch_id, - 'out_trade_no': trade_sn, - 'nonce_str': self._generate_nonce(), - } - params['sign'] = self._generate_sign(params) - - import requests - xml_data = self._dict_to_xml(params) - response = requests.post(self.CLOSE_ORDER_URL, data=xml_data.encode('utf-8')) - result = self._xml_to_dict(response.text) - - return result.get('result_code') == 'SUCCESS' - - def query_trade(self, trade_sn: str) -> Optional[NotifyResult]: - """查询微信交易""" - params = { - 'appid': self.appid, - 'mch_id': self.mch_id, - 'out_trade_no': trade_sn, - 'nonce_str': self._generate_nonce(), - } - params['sign'] = self._generate_sign(params) - - import requests - xml_data = self._dict_to_xml(params) - response = requests.post(self.ORDER_QUERY_URL, data=xml_data.encode('utf-8')) - result = self._xml_to_dict(response.text) - - if result.get('trade_state') == 'SUCCESS': - return self.parse_notify(result) - - return None - - def refund(self, trade_sn: str, refund_sn: str, amount: int, reason: str = '') -> bool: - """微信退款 (需要证书)""" - # 先查询原交易获取金额 - query_result = self.query_trade(trade_sn) - if not query_result: - return False - - params = { - 'appid': self.appid, - 'mch_id': self.mch_id, - 'nonce_str': self._generate_nonce(), - 'out_trade_no': trade_sn, - 'out_refund_no': refund_sn, - 'total_fee': str(query_result.pay_amount), - 'refund_fee': str(amount), - 'refund_desc': reason or '用户申请退款', - } - params['sign'] = self._generate_sign(params) - - import requests - xml_data = self._dict_to_xml(params) - - # 退款需要证书 - response = requests.post( - self.REFUND_URL, - data=xml_data.encode('utf-8'), - cert=(self.cert_path, self.key_path) - ) - result = self._xml_to_dict(response.text) - - return result.get('result_code') == 'SUCCESS' - - def success_response(self) -> str: - """微信回调成功响应""" - return '' - - def fail_response(self) -> str: - """微信回调失败响应""" - return '' - - def _generate_nonce(self) -> str: - """生成随机字符串""" - return uuid.uuid4().hex - - def _generate_sign(self, params: dict) -> str: - """生成MD5签名""" - # 排序并拼接 - sorted_params = sorted([(k, v) for k, v in params.items() if v and k != 'sign']) - sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params]) - sign_str += f'&key={self.mch_key}' - - # MD5签名 - return hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper() - - def _dict_to_xml(self, data: dict) -> str: - """字典转XML""" - xml = [''] - for k, v in data.items(): - if isinstance(v, str): - xml.append(f'<{k}>') - else: - xml.append(f'<{k}>{v}') - xml.append('') - return ''.join(xml) - - def _xml_to_dict(self, xml_str: str) -> dict: - """XML转字典""" - root = ET.fromstring(xml_str) - return {child.tag: child.text for child in root} diff --git a/next-project/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt b/next-project/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt deleted file mode 100644 index 345aba6e..00000000 --- a/next-project/addons/Universal_Payment_Module copy/4_卡若配置/env.example.txt +++ /dev/null @@ -1,101 +0,0 @@ -# ============================================================================ -# 卡若私域支付配置 (Karuo Payment Config) -# ============================================================================ -# ⚠️ 警告: 此文件包含敏感信息,请勿提交到 Git! -# 使用方法: 复制此文件为 .env 并填入真实值 -# ============================================================================ - -# ---------------------------------------------------------------------------- -# 1. 基础环境 -# ---------------------------------------------------------------------------- -APP_ENV=production -APP_NAME=卡若私域 -APP_URL=https://www.lytiao.com -APP_CURRENCY=CNY - -# ---------------------------------------------------------------------------- -# 2. 数据库 (卡若私域数据库) -# ---------------------------------------------------------------------------- -DB_CONNECTION=mysql -DB_HOST=10.88.182.62 -DB_PORT=3306 -DB_DATABASE=payment_db -DB_USERNAME=root -DB_PASSWORD=Vtka(agu)-1 - -# ---------------------------------------------------------------------------- -# 3. 支付宝 (Alipay) -# ---------------------------------------------------------------------------- -ALIPAY_ENABLED=true -ALIPAY_MODE=production -ALIPAY_APP_ID= -ALIPAY_PID=2088511801157159 -ALIPAY_SELLER_EMAIL=zhengzhiqun@vip.qq.com -ALIPAY_PRIVATE_KEY= -ALIPAY_PUBLIC_KEY= -ALIPAY_MD5_KEY=lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp - -# ---------------------------------------------------------------------------- -# 4. 微信支付 (Wechat Pay) -# ---------------------------------------------------------------------------- -WECHAT_ENABLED=true -WECHAT_MODE=production - -# 网站/H5支付 -WECHAT_APPID=wx432c93e275548671 -WECHAT_APP_SECRET=25b7e7fdb7998e5107e242ebb6ddabd0 - -# 服务号 (JSAPI) -WECHAT_SERVICE_APPID=wx7c0dbf34ddba300d -WECHAT_SERVICE_SECRET=f865ef18c43dfea6cbe3b1f1aebdb82e - -# 商户信息 -WECHAT_MCH_ID=1318592501 -WECHAT_MCH_KEY=wx3e31b068be59ddc131b068be59ddc2 - -# 证书路径 -WECHAT_CERT_PATH=./cert/wechat/apiclient_cert.pem -WECHAT_KEY_PATH=./cert/wechat/apiclient_key.pem - -# MP文件验证码 -WECHAT_MP_VERIFY=SP8AfZJyAvprRORT - -# ---------------------------------------------------------------------------- -# 5. PayPal (暂未启用) -# ---------------------------------------------------------------------------- -PAYPAL_ENABLED=false -PAYPAL_MODE=sandbox -PAYPAL_CLIENT_ID= -PAYPAL_CLIENT_SECRET= - -# ---------------------------------------------------------------------------- -# 6. Stripe (暂未启用) -# ---------------------------------------------------------------------------- -STRIPE_ENABLED=false -STRIPE_MODE=test -STRIPE_PUBLIC_KEY= -STRIPE_SECRET_KEY= -STRIPE_WEBHOOK_SECRET= - -# ---------------------------------------------------------------------------- -# 7. USDT (暂未启用) -# ---------------------------------------------------------------------------- -USDT_ENABLED=false -USDT_GATEWAY_TYPE=nowpayments -NOWPAYMENTS_API_KEY= -NOWPAYMENTS_IPN_SECRET= - -# ---------------------------------------------------------------------------- -# 8. 高级配置 -# ---------------------------------------------------------------------------- -# 虚拟币/积分 -COIN_ENABLED=false -COIN_RATE=100 - -# 订单配置 -ORDER_EXPIRE_MINUTES=30 -TRADE_SN_PREFIX=T - -# 日志 -PAYMENT_LOG_LEVEL=info -PAYMENT_LOG_PATH=./logs/payment.log diff --git a/next-project/addons/Universal_Payment_Module copy/README.md b/next-project/addons/Universal_Payment_Module copy/README.md deleted file mode 100644 index be151a97..00000000 --- a/next-project/addons/Universal_Payment_Module copy/README.md +++ /dev/null @@ -1,142 +0,0 @@ -# 🌐 全球通用支付模块 (Universal Payment Module) v4.0 - -> **配置驱动 (Configuration-Driven)** | **API 优先 (API-First)** | **AI 智能对接** -> -> 让任何语言的项目在 5 分钟内接入支付宝、微信支付、PayPal、Stripe 和 USDT - -## 📂 模块结构 - -``` -Universal_Payment_Module/ -├── 1_核心设计_通用协议/ # [灵魂] 定义了支付的"法律" -│ ├── 标准配置模板.yaml # 填空即可配置所有支付参数 -│ ├── API接口定义.md # 无论用什么语言,接口都长这样 -│ ├── 业务逻辑与模型.md # 数据库表结构设计 (Order/PayTrade) -│ └── 安全与合规.md # 支付安全最佳实践 -│ -├── 2_智能对接_AI指令/ # [工具] AI 编译器 -│ ├── 通用集成指令.md # 发给 AI,自动生成代码 -│ └── Cursor规则.md # Cursor IDE 专用规则 -│ -├── 3_逻辑参考_通用实现/ # [参考] 可直接复用的代码 -│ ├── 前端收银台Demo.html # 原生 JS 实现的通用收银台 -│ ├── 后端源码/ # 多语言参考实现 -│ │ ├── php/ # PHP (Laravel/Symfony) -│ │ ├── python/ # Python (FastAPI/Django) -│ │ ├── nodejs/ # Node.js (Express/NestJS) -│ │ └── java/ # Java (Spring Boot) -│ └── 前端模板/ # Vue/React/原生JS 模板 -│ -├── 4_卡若配置/ # [私有] 卡若的支付密钥 (勿提交Git) -│ └── .env.example # 配置示例 -│ -└── README.md # 本说明文档 -``` - -## 🚀 极速对接 (3步完成) - -### 第一步:配置 (Config) -```bash -# 1. 复制配置模板到你的项目 -cp 1_核心设计_通用协议/标准配置模板.yaml your-project/.env - -# 2. 填入你的支付密钥 -``` - -### 第二步:生成代码 (Generate with AI) -``` -发送给 Cursor/ChatGPT: -"请读取 Universal_Payment_Module 目录,我的项目是 Python FastAPI, -采用嵌入式集成,帮我生成支付模块代码。" -``` - -### 第三步:前端接入 (Frontend) -```javascript -// 只需调用一个 API -const result = await fetch('/api/payment/checkout', { - method: 'POST', - body: JSON.stringify({ order_sn: '202401170001', gateway: 'wechat_jsapi' }) -}); -``` - -## 🌍 支持的支付渠道 - -| 渠道 | 能力 | 场景 | 状态 | -|:---|:---|:---|:---| -| **支付宝 Alipay** | 扫码/H5/APP/小程序 | 中国市场 (CNY) | ✅ 已实现 | -| **微信支付 Wechat** | JSAPI/Native/H5/APP/小程序 | 中国市场 (CNY) | ✅ 已实现 | -| **PayPal** | 信用卡/订阅 | 全球市场 (USD/EUR) | ✅ 已实现 | -| **Stripe** | 信用卡/订阅/Apple Pay | 全球市场 | ✅ 已实现 | -| **USDT (TRC20)** | 链上转账/监听 | Web3/抗审查 | ✅ 已实现 | - -## ✨ v4.0 核心特性 - -### 1. 配置驱动 (Zero-Code Config) -- 所有密钥通过环境变量注入,无需改动代码 -- 支持多环境切换 (development/production) - -### 2. 工厂模式 (Payment Factory) -```python -# 所有支付网关统一接口 -payment = PaymentFactory.create('wechat') -result = payment.create_trade(order) -``` - -### 3. 幂等性保障 (Idempotency) -- 回调通知自动去重 -- 订单状态机管理 - -### 4. AI 智能生成 -- 提供 Cursor/Copilot 专用提示词 -- 一键生成任意语言的完整实现 - -## 📖 快速参考 - -### 创建订单 -```http -POST /api/payment/create_order -Content-Type: application/json - -{ - "user_id": "u1001", - "title": "VIP会员", - "amount": 99.00, - "currency": "CNY", - "product_id": "vip_monthly" -} -``` - -### 发起支付 -```http -POST /api/payment/checkout -Content-Type: application/json - -{ - "order_sn": "202401170001", - "gateway": "wechat_jsapi", - "openid": "oXxx..." -} -``` - -### 支付状态查询 -```http -GET /api/payment/status/202401170001 -``` - -## 🔐 安全须知 - -1. **密钥安全**: 所有密钥存放在 `.env`,**绝不提交到 Git** -2. **HTTPS 强制**: 生产环境必须启用 HTTPS -3. **签名验证**: 所有回调必须验签 -4. **金额校验**: 支付金额必须与订单金额匹配 - -## 📚 相关文档 - -- [API 接口定义](./1_核心设计_通用协议/API接口定义.md) -- [数据库模型](./1_核心设计_通用协议/业务逻辑与模型.md) -- [AI 集成指令](./2_智能对接_AI指令/通用集成指令.md) -- [Cursor 规则](./2_智能对接_AI指令/Cursor规则.md) - ---- - -**作者**: 卡若 | **联系**: 28533368 (微信) | **更新**: 2026-01-17 diff --git a/next-project/api/soul导师顾问接口.md b/next-project/api/soul导师顾问接口.md deleted file mode 100644 index 7a1c2636..00000000 --- a/next-project/api/soul导师顾问接口.md +++ /dev/null @@ -1,413 +0,0 @@ -# 对外获客线索上报接口文档(V1) - -## 一、接口概述 - -- **接口名称**:对外获客线索上报接口 -- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。 -- **接口协议**:HTTP -- **请求方式**:`POST` -- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios` - -> 具体 URL 以实际环境配置为准。 - -- **数据格式**: - - 推荐:`application/json` - - 兼容:`application/x-www-form-urlencoded` -- **字符编码**:`UTF-8` - ---- - -## 二、鉴权与签名 - -### 2.1 必填鉴权字段 - -| 字段名 | 类型 | 必填 | 说明 | -|-------------|--------|------|---------------------------------------| -| `apiKey` | string | 是 | 分配给第三方的接口密钥(每个任务唯一)| -| `sign` | string | 是 | 签名值 | -| `timestamp` | int | 是 | 秒级时间戳(与服务器时间差不超过 5 分钟) | - -### 2.2 时间戳校验 - -服务器会校验 `timestamp` 是否在当前时间前后 **5 分钟** 内: - -- 通过条件:`|server_time - timestamp| <= 300` -- 超出范围则返回:`请求已过期` - -### 2.3 签名生成规则 - -接口采用自定义签名机制。**签名字段为 `sign`,生成步骤如下:** - -假设本次请求的所有参数为 `params`,其中包括业务参数 + `apiKey` + `timestamp` + `sign` + 可能存在的 `portrait` 对象。 - -#### 第一步:移除特定字段 - -从 `params` 中移除以下字段: - -- `sign` —— 自身不参与签名 -- `apiKey` —— 不参与参数拼接,仅在最后一步参与二次 MD5 -- `portrait` —— 整个画像对象不参与签名(即使内部还有子字段) - -> 说明:`portrait` 通常是一个 JSON 对象,字段较多,为避免签名实现复杂且双方难以对齐,统一不参与签名。 - -#### 第二步:移除空值字段 - -从剩余参数中,移除值为: - -- `null` -- 空字符串 `''` - -的字段,这些字段不参与签名。 - -#### 第三步:按参数名升序排序 - -对剩余参数按**参数名(键名)升序排序**,排序规则为标准的 ASCII 升序: - -```text -例如: name, phone, source, timestamp -``` - -#### 第四步:拼接参数值 - -将排序后的参数 **只取“值”**,按顺序直接拼接为一个字符串,中间不加任何分隔符: - -- 示例: - 排序后参数为: - - ```text - name = 张三 - phone = 13800000000 - source = 微信广告 - timestamp = 1710000000 - ``` - - 则拼接: - - ```text - stringToSign = "张三13800000000微信广告1710000000" - ``` - -#### 第五步:第一次 MD5 - -对上一步拼接得到的字符串做一次 MD5: - -\[ -\text{firstMd5} = \text{MD5}(\text{stringToSign}) -\] - -#### 第六步:拼接 apiKey 再次 MD5 - -将第一步的结果与 `apiKey` 直接拼接,再做一次 MD5,得到最终签名值: - -\[ -\text{sign} = \text{MD5}(\text{firstMd5} + \text{apiKey}) -\] - -#### 第七步:放入请求 - -将第六步得到的 `sign` 填入请求参数中的 `sign` 字段即可。 - -> 建议: -> - 使用小写 MD5 字符串(双方约定统一即可)。 -> - 请确保参与签名的参数与最终请求发送的参数一致(包括是否传空值)。 - -### 2.4 签名示例(PHP 伪代码) - -```php -$params = [ - 'apiKey' => 'YOUR_API_KEY', - 'timestamp' => '1710000000', - 'phone' => '13800000000', - 'name' => '张三', - 'source' => '微信广告', - 'remark' => '通过H5落地页留资', - // 'portrait' => [...], // 如有画像,这里会存在,但不参与签名 - // 'sign' => '待生成', -]; - -// 1. 去掉 sign、apiKey、portrait -unset($params['sign'], $params['apiKey'], $params['portrait']); - -// 2. 去掉空值 -$params = array_filter($params, function($value) { - return !is_null($value) && $value !== ''; -}); - -// 3. 按键名升序排序 -ksort($params); - -// 4. 拼接参数值 -$stringToSign = implode('', array_values($params)); - -// 5. 第一次 MD5 -$firstMd5 = md5($stringToSign); - -// 6. 第二次 MD5(拼接 apiKey) -$apiKey = 'YOUR_API_KEY'; -$sign = md5($firstMd5 . $apiKey); - -// 将 $sign 作为字段发送 -$params['sign'] = $sign; -``` - ---- - -## 三、请求参数说明 - -### 3.1 主标识字段(至少传一个) - -| 字段名 | 类型 | 必填 | 说明 | -|-----------|--------|------|-------------------------------------------| -| `wechatId`| string | 否 | 微信号,存在时优先作为主标识 | -| `phone` | string | 否 | 手机号,当 `wechatId` 为空时用作主标识 | - -### 3.2 基础信息字段 - -| 字段名 | 类型 | 必填 | 说明 | -|------------|--------|------|-------------------------| -| `name` | string | 否 | 客户姓名 | -| `source` | string | 否 | 线索来源描述,如“百度推广”、“抖音直播间” | -| `remark` | string | 否 | 备注信息 | -| `tags` | string | 否 | 逗号分隔的“微信标签”,如:`"高意向,电商,女装"` | -| `siteTags` | string | 否 | 逗号分隔的“站内标签”,用于站内进一步细分 | - - -### 3.3 用户画像字段 `portrait`(可选) - -`portrait` 为一个对象(JSON),用于记录用户的行为画像数据。 - -#### 3.3.1 基本示例 - -```json -"portrait": { - "type": 1, - "source": 1, - "sourceData": { - "age": 28, - "gender": "female", - "city": "上海", - "productId": "P12345", - "pageUrl": "https://example.com/product/123" - }, - "remark": "画像-基础属性", - "uniqueId": "user_13800000000_20250301_001" -} -``` - -#### 3.3.2 字段详细说明 - -| 字段名 | 类型 | 必填 | 说明 | -|-----------------------|--------|------|----------------------------------------| -| `portrait.type` | int | 否 | 画像类型,枚举值:
0-浏览
1-点击
2-下单/购买
3-注册
4-互动
默认值:0 | -| `portrait.source` | int | 否 | 画像来源,枚举值:
0-本站
1-老油条
2-老坑爹
默认值:0 | -| `portrait.sourceData` | object | 否 | 画像明细数据(键值对,会存储为 JSON 格式)
可包含任意业务相关的键值对,如:年龄、性别、城市、商品ID、页面URL等 | -| `portrait.remark` | string | 否 | 画像备注信息,最大长度100字符 | -| `portrait.uniqueId` | string | 否 | 画像去重用唯一 ID
用于防止重复记录,相同 `uniqueId` 的画像数据在半小时内会被合并统计(count字段累加)
建议格式:`{来源标识}_{用户标识}_{时间戳}_{序号}` | - -#### 3.3.3 画像类型(type)说明 - -| 值 | 类型 | 说明 | 适用场景 | -|---|------|------|---------| -| 0 | 浏览 | 用户浏览了页面或内容 | 页面访问、商品浏览、文章阅读等 | -| 1 | 点击 | 用户点击了某个元素 | 按钮点击、链接点击、广告点击等 | -| 2 | 下单/购买 | 用户完成了购买行为 | 订单提交、支付完成等 | -| 3 | 注册 | 用户完成了注册 | 账号注册、会员注册等 | -| 4 | 互动 | 用户进行了互动行为 | 点赞、评论、分享、咨询等 | - -#### 3.3.4 画像来源(source)说明 - -| 值 | 来源 | 说明 | -|---|------|------| -| 0 | 本站 | 来自本站的数据 | -| 1 | 老油条 | 来自"老油条"系统的数据 | -| 2 | 老坑爹 | 来自"老坑爹"系统的数据 | - -#### 3.3.5 sourceData 数据格式说明 - -`sourceData` 是一个 JSON 对象,可以包含任意业务相关的键值对。常见字段示例: - -```json -{ - "age": 28, - "gender": "female", - "city": "上海", - "province": "上海市", - "productId": "P12345", - "productName": "商品名称", - "category": "女装", - "price": 299.00, - "pageUrl": "https://example.com/product/123", - "referrer": "https://www.baidu.com", - "device": "mobile", - "browser": "WeChat" -} -``` - -> **注意**: -> - `sourceData` 中的数据类型可以是字符串、数字、布尔值等 -> - 嵌套对象会被序列化为 JSON 字符串存储 -> - 建议根据实际业务需求定义字段结构 - -#### 3.3.6 uniqueId 去重机制说明 - -- **作用**:防止重复记录相同的画像数据 -- **规则**:相同 `uniqueId` 的画像数据在 **半小时内** 会被合并统计,`count` 字段会自动累加 -- **建议格式**:`{来源标识}_{用户标识}_{时间戳}_{序号}` - - 示例:`site_13800000000_1710000000_001` - - 示例:`wechat_wxid_abc123_1710000000_001` -- **注意事项**: - - 如果不传 `uniqueId`,系统会为每条画像数据创建新记录 - - 如果需要在半小时内多次统计同一行为,应使用相同的 `uniqueId` - - 如果需要在半小时后重新统计,应使用不同的 `uniqueId`(建议修改时间戳部分) - -> **重要提示**:`portrait` **整体不参与签名计算**,但会参与业务处理。系统会根据 `uniqueId` 自动处理去重和统计。 - ---- - -## 四、请求示例 - -### 4.1 JSON 请求示例(无画像) - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "phone": "13800000000", - "name": "张三", - "source": "微信广告", - "remark": "通过H5落地页留资", - "tags": "高意向,电商", - "siteTags": "新客,女装", - "sign": "根据签名规则生成的MD5字符串" -} -``` - -### 4.2 JSON 请求示例(带微信号与画像) - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "wechatId": "wxid_abcdefg123", - "phone": "13800000001", - "name": "李四", - "source": "小程序落地页", - "remark": "点击【立即咨询】按钮", - "tags": "中意向,直播", - "siteTags": "复购,高客单", - "portrait": { - "type": 1, - "source": 0, - "sourceData": { - "age": 28, - "gender": "female", - "city": "上海", - "pageUrl": "https://example.com/product/123", - "productId": "P12345" - }, - "remark": "画像-点击行为", - "uniqueId": "site_13800000001_1710000000_001" - }, - "sign": "根据签名规则生成的MD5字符串" -} -``` - -### 4.3 JSON 请求示例(多种画像类型) - -#### 4.3.1 浏览行为画像 - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "phone": "13800000002", - "name": "王五", - "source": "百度推广", - "portrait": { - "type": 0, - "source": 0, - "sourceData": { - "pageUrl": "https://example.com/product/456", - "productName": "商品名称", - "category": "女装", - "stayTime": 120, - "device": "mobile" - }, - "remark": "商品浏览", - "uniqueId": "site_13800000002_1710000000_001" - }, - "sign": "根据签名规则生成的MD5字符串" -} -``` - - -``` - ---- - -## 五、响应说明 - -### 5.1 成功响应 - -**1)新增线索成功** - -```json -{ - "code": 200, - "message": "新增成功", - "data": "13800000000" -} -``` - -**2)线索已存在** - -```json -{ - "code": 200, - "message": "已存在", - "data": "13800000000" -} -``` - -> `data` 字段返回本次线索的主标识 `wechatId` 或 `phone`。 - -### 5.2 常见错误响应 - -```json -{ "code": 400, "message": "apiKey不能为空", "data": null } -{ "code": 400, "message": "sign不能为空", "data": null } -{ "code": 400, "message": "timestamp不能为空", "data": null } -{ "code": 400, "message": "请求已过期", "data": null } - -{ "code": 401, "message": "无效的apiKey", "data": null } -{ "code": 401, "message": "签名验证失败", "data": null } - -{ "code": 500, "message": "系统错误: 具体错误信息", "data": null } -``` - ---- - - -## 六、常见问题(FAQ) - -### Q1: 如果同一个用户多次上报相同的行为,会如何处理? - -**A**: 如果使用相同的 `uniqueId`,系统会在半小时内合并统计,`count` 字段会累加。如果使用不同的 `uniqueId`,会创建多条记录。 - -### Q2: portrait 字段是否必须传递? - -**A**: 不是必须的。`portrait` 字段是可选的,只有在需要记录用户画像数据时才传递。 - -### Q3: sourceData 中可以存储哪些类型的数据? - -**A**: `sourceData` 是一个 JSON 对象,可以存储任意键值对。支持字符串、数字、布尔值等基本类型,嵌套对象会被序列化为 JSON 字符串。 - -### Q4: uniqueId 的作用是什么? - -**A**: `uniqueId` 用于防止重复记录。相同 `uniqueId` 的画像数据在半小时内会被合并统计,避免重复数据。 - -### Q5: 画像数据如何与用户关联? - -**A**: 系统会根据请求中的 `wechatId` 或 `phone` 自动匹配 `traffic_pool` 表中的用户,并将画像数据关联到对应的 `trafficPoolId`。 - ---- diff --git a/next-project/api/soul资源对接接口.md b/next-project/api/soul资源对接接口.md deleted file mode 100644 index 7a1c2636..00000000 --- a/next-project/api/soul资源对接接口.md +++ /dev/null @@ -1,413 +0,0 @@ -# 对外获客线索上报接口文档(V1) - -## 一、接口概述 - -- **接口名称**:对外获客线索上报接口 -- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。 -- **接口协议**:HTTP -- **请求方式**:`POST` -- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios` - -> 具体 URL 以实际环境配置为准。 - -- **数据格式**: - - 推荐:`application/json` - - 兼容:`application/x-www-form-urlencoded` -- **字符编码**:`UTF-8` - ---- - -## 二、鉴权与签名 - -### 2.1 必填鉴权字段 - -| 字段名 | 类型 | 必填 | 说明 | -|-------------|--------|------|---------------------------------------| -| `apiKey` | string | 是 | 分配给第三方的接口密钥(每个任务唯一)| -| `sign` | string | 是 | 签名值 | -| `timestamp` | int | 是 | 秒级时间戳(与服务器时间差不超过 5 分钟) | - -### 2.2 时间戳校验 - -服务器会校验 `timestamp` 是否在当前时间前后 **5 分钟** 内: - -- 通过条件:`|server_time - timestamp| <= 300` -- 超出范围则返回:`请求已过期` - -### 2.3 签名生成规则 - -接口采用自定义签名机制。**签名字段为 `sign`,生成步骤如下:** - -假设本次请求的所有参数为 `params`,其中包括业务参数 + `apiKey` + `timestamp` + `sign` + 可能存在的 `portrait` 对象。 - -#### 第一步:移除特定字段 - -从 `params` 中移除以下字段: - -- `sign` —— 自身不参与签名 -- `apiKey` —— 不参与参数拼接,仅在最后一步参与二次 MD5 -- `portrait` —— 整个画像对象不参与签名(即使内部还有子字段) - -> 说明:`portrait` 通常是一个 JSON 对象,字段较多,为避免签名实现复杂且双方难以对齐,统一不参与签名。 - -#### 第二步:移除空值字段 - -从剩余参数中,移除值为: - -- `null` -- 空字符串 `''` - -的字段,这些字段不参与签名。 - -#### 第三步:按参数名升序排序 - -对剩余参数按**参数名(键名)升序排序**,排序规则为标准的 ASCII 升序: - -```text -例如: name, phone, source, timestamp -``` - -#### 第四步:拼接参数值 - -将排序后的参数 **只取“值”**,按顺序直接拼接为一个字符串,中间不加任何分隔符: - -- 示例: - 排序后参数为: - - ```text - name = 张三 - phone = 13800000000 - source = 微信广告 - timestamp = 1710000000 - ``` - - 则拼接: - - ```text - stringToSign = "张三13800000000微信广告1710000000" - ``` - -#### 第五步:第一次 MD5 - -对上一步拼接得到的字符串做一次 MD5: - -\[ -\text{firstMd5} = \text{MD5}(\text{stringToSign}) -\] - -#### 第六步:拼接 apiKey 再次 MD5 - -将第一步的结果与 `apiKey` 直接拼接,再做一次 MD5,得到最终签名值: - -\[ -\text{sign} = \text{MD5}(\text{firstMd5} + \text{apiKey}) -\] - -#### 第七步:放入请求 - -将第六步得到的 `sign` 填入请求参数中的 `sign` 字段即可。 - -> 建议: -> - 使用小写 MD5 字符串(双方约定统一即可)。 -> - 请确保参与签名的参数与最终请求发送的参数一致(包括是否传空值)。 - -### 2.4 签名示例(PHP 伪代码) - -```php -$params = [ - 'apiKey' => 'YOUR_API_KEY', - 'timestamp' => '1710000000', - 'phone' => '13800000000', - 'name' => '张三', - 'source' => '微信广告', - 'remark' => '通过H5落地页留资', - // 'portrait' => [...], // 如有画像,这里会存在,但不参与签名 - // 'sign' => '待生成', -]; - -// 1. 去掉 sign、apiKey、portrait -unset($params['sign'], $params['apiKey'], $params['portrait']); - -// 2. 去掉空值 -$params = array_filter($params, function($value) { - return !is_null($value) && $value !== ''; -}); - -// 3. 按键名升序排序 -ksort($params); - -// 4. 拼接参数值 -$stringToSign = implode('', array_values($params)); - -// 5. 第一次 MD5 -$firstMd5 = md5($stringToSign); - -// 6. 第二次 MD5(拼接 apiKey) -$apiKey = 'YOUR_API_KEY'; -$sign = md5($firstMd5 . $apiKey); - -// 将 $sign 作为字段发送 -$params['sign'] = $sign; -``` - ---- - -## 三、请求参数说明 - -### 3.1 主标识字段(至少传一个) - -| 字段名 | 类型 | 必填 | 说明 | -|-----------|--------|------|-------------------------------------------| -| `wechatId`| string | 否 | 微信号,存在时优先作为主标识 | -| `phone` | string | 否 | 手机号,当 `wechatId` 为空时用作主标识 | - -### 3.2 基础信息字段 - -| 字段名 | 类型 | 必填 | 说明 | -|------------|--------|------|-------------------------| -| `name` | string | 否 | 客户姓名 | -| `source` | string | 否 | 线索来源描述,如“百度推广”、“抖音直播间” | -| `remark` | string | 否 | 备注信息 | -| `tags` | string | 否 | 逗号分隔的“微信标签”,如:`"高意向,电商,女装"` | -| `siteTags` | string | 否 | 逗号分隔的“站内标签”,用于站内进一步细分 | - - -### 3.3 用户画像字段 `portrait`(可选) - -`portrait` 为一个对象(JSON),用于记录用户的行为画像数据。 - -#### 3.3.1 基本示例 - -```json -"portrait": { - "type": 1, - "source": 1, - "sourceData": { - "age": 28, - "gender": "female", - "city": "上海", - "productId": "P12345", - "pageUrl": "https://example.com/product/123" - }, - "remark": "画像-基础属性", - "uniqueId": "user_13800000000_20250301_001" -} -``` - -#### 3.3.2 字段详细说明 - -| 字段名 | 类型 | 必填 | 说明 | -|-----------------------|--------|------|----------------------------------------| -| `portrait.type` | int | 否 | 画像类型,枚举值:
0-浏览
1-点击
2-下单/购买
3-注册
4-互动
默认值:0 | -| `portrait.source` | int | 否 | 画像来源,枚举值:
0-本站
1-老油条
2-老坑爹
默认值:0 | -| `portrait.sourceData` | object | 否 | 画像明细数据(键值对,会存储为 JSON 格式)
可包含任意业务相关的键值对,如:年龄、性别、城市、商品ID、页面URL等 | -| `portrait.remark` | string | 否 | 画像备注信息,最大长度100字符 | -| `portrait.uniqueId` | string | 否 | 画像去重用唯一 ID
用于防止重复记录,相同 `uniqueId` 的画像数据在半小时内会被合并统计(count字段累加)
建议格式:`{来源标识}_{用户标识}_{时间戳}_{序号}` | - -#### 3.3.3 画像类型(type)说明 - -| 值 | 类型 | 说明 | 适用场景 | -|---|------|------|---------| -| 0 | 浏览 | 用户浏览了页面或内容 | 页面访问、商品浏览、文章阅读等 | -| 1 | 点击 | 用户点击了某个元素 | 按钮点击、链接点击、广告点击等 | -| 2 | 下单/购买 | 用户完成了购买行为 | 订单提交、支付完成等 | -| 3 | 注册 | 用户完成了注册 | 账号注册、会员注册等 | -| 4 | 互动 | 用户进行了互动行为 | 点赞、评论、分享、咨询等 | - -#### 3.3.4 画像来源(source)说明 - -| 值 | 来源 | 说明 | -|---|------|------| -| 0 | 本站 | 来自本站的数据 | -| 1 | 老油条 | 来自"老油条"系统的数据 | -| 2 | 老坑爹 | 来自"老坑爹"系统的数据 | - -#### 3.3.5 sourceData 数据格式说明 - -`sourceData` 是一个 JSON 对象,可以包含任意业务相关的键值对。常见字段示例: - -```json -{ - "age": 28, - "gender": "female", - "city": "上海", - "province": "上海市", - "productId": "P12345", - "productName": "商品名称", - "category": "女装", - "price": 299.00, - "pageUrl": "https://example.com/product/123", - "referrer": "https://www.baidu.com", - "device": "mobile", - "browser": "WeChat" -} -``` - -> **注意**: -> - `sourceData` 中的数据类型可以是字符串、数字、布尔值等 -> - 嵌套对象会被序列化为 JSON 字符串存储 -> - 建议根据实际业务需求定义字段结构 - -#### 3.3.6 uniqueId 去重机制说明 - -- **作用**:防止重复记录相同的画像数据 -- **规则**:相同 `uniqueId` 的画像数据在 **半小时内** 会被合并统计,`count` 字段会自动累加 -- **建议格式**:`{来源标识}_{用户标识}_{时间戳}_{序号}` - - 示例:`site_13800000000_1710000000_001` - - 示例:`wechat_wxid_abc123_1710000000_001` -- **注意事项**: - - 如果不传 `uniqueId`,系统会为每条画像数据创建新记录 - - 如果需要在半小时内多次统计同一行为,应使用相同的 `uniqueId` - - 如果需要在半小时后重新统计,应使用不同的 `uniqueId`(建议修改时间戳部分) - -> **重要提示**:`portrait` **整体不参与签名计算**,但会参与业务处理。系统会根据 `uniqueId` 自动处理去重和统计。 - ---- - -## 四、请求示例 - -### 4.1 JSON 请求示例(无画像) - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "phone": "13800000000", - "name": "张三", - "source": "微信广告", - "remark": "通过H5落地页留资", - "tags": "高意向,电商", - "siteTags": "新客,女装", - "sign": "根据签名规则生成的MD5字符串" -} -``` - -### 4.2 JSON 请求示例(带微信号与画像) - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "wechatId": "wxid_abcdefg123", - "phone": "13800000001", - "name": "李四", - "source": "小程序落地页", - "remark": "点击【立即咨询】按钮", - "tags": "中意向,直播", - "siteTags": "复购,高客单", - "portrait": { - "type": 1, - "source": 0, - "sourceData": { - "age": 28, - "gender": "female", - "city": "上海", - "pageUrl": "https://example.com/product/123", - "productId": "P12345" - }, - "remark": "画像-点击行为", - "uniqueId": "site_13800000001_1710000000_001" - }, - "sign": "根据签名规则生成的MD5字符串" -} -``` - -### 4.3 JSON 请求示例(多种画像类型) - -#### 4.3.1 浏览行为画像 - -```json -{ - "apiKey": "YOUR_API_KEY", - "timestamp": 1710000000, - "phone": "13800000002", - "name": "王五", - "source": "百度推广", - "portrait": { - "type": 0, - "source": 0, - "sourceData": { - "pageUrl": "https://example.com/product/456", - "productName": "商品名称", - "category": "女装", - "stayTime": 120, - "device": "mobile" - }, - "remark": "商品浏览", - "uniqueId": "site_13800000002_1710000000_001" - }, - "sign": "根据签名规则生成的MD5字符串" -} -``` - - -``` - ---- - -## 五、响应说明 - -### 5.1 成功响应 - -**1)新增线索成功** - -```json -{ - "code": 200, - "message": "新增成功", - "data": "13800000000" -} -``` - -**2)线索已存在** - -```json -{ - "code": 200, - "message": "已存在", - "data": "13800000000" -} -``` - -> `data` 字段返回本次线索的主标识 `wechatId` 或 `phone`。 - -### 5.2 常见错误响应 - -```json -{ "code": 400, "message": "apiKey不能为空", "data": null } -{ "code": 400, "message": "sign不能为空", "data": null } -{ "code": 400, "message": "timestamp不能为空", "data": null } -{ "code": 400, "message": "请求已过期", "data": null } - -{ "code": 401, "message": "无效的apiKey", "data": null } -{ "code": 401, "message": "签名验证失败", "data": null } - -{ "code": 500, "message": "系统错误: 具体错误信息", "data": null } -``` - ---- - - -## 六、常见问题(FAQ) - -### Q1: 如果同一个用户多次上报相同的行为,会如何处理? - -**A**: 如果使用相同的 `uniqueId`,系统会在半小时内合并统计,`count` 字段会累加。如果使用不同的 `uniqueId`,会创建多条记录。 - -### Q2: portrait 字段是否必须传递? - -**A**: 不是必须的。`portrait` 字段是可选的,只有在需要记录用户画像数据时才传递。 - -### Q3: sourceData 中可以存储哪些类型的数据? - -**A**: `sourceData` 是一个 JSON 对象,可以存储任意键值对。支持字符串、数字、布尔值等基本类型,嵌套对象会被序列化为 JSON 字符串。 - -### Q4: uniqueId 的作用是什么? - -**A**: `uniqueId` 用于防止重复记录。相同 `uniqueId` 的画像数据在半小时内会被合并统计,避免重复数据。 - -### Q5: 画像数据如何与用户关联? - -**A**: 系统会根据请求中的 `wechatId` 或 `phone` 自动匹配 `traffic_pool` 表中的用户,并将画像数据关联到对应的 `trafficPoolId`。 - ---- diff --git a/next-project/app/admin/chapters/page.tsx b/next-project/app/admin/chapters/page.tsx deleted file mode 100644 index 4ae054d0..00000000 --- a/next-project/app/admin/chapters/page.tsx +++ /dev/null @@ -1,293 +0,0 @@ -'use client' - -import { useState, useEffect } from 'react' -import Link from 'next/link' - -interface Section { - id: string - title: string - price: number - isFree: boolean - status: string -} - -interface Chapter { - id: string - title: string - sections?: Section[] - price?: number - isFree?: boolean - status?: string -} - -interface Part { - id: string - title: string - type: string - chapters: Chapter[] -} - -interface Stats { - totalSections: number - freeSections: number - paidSections: number - totalParts: number -} - -export default function ChaptersManagement() { - const [structure, setStructure] = useState([]) - const [stats, setStats] = useState(null) - const [loading, setLoading] = useState(true) - const [expandedParts, setExpandedParts] = useState([]) - const [editingSection, setEditingSection] = useState(null) - const [editPrice, setEditPrice] = useState(1) - - useEffect(() => { - loadChapters() - }, []) - - const loadChapters = async () => { - try { - const response = await fetch('/api/admin/chapters') - const data = await response.json() - if (data.success) { - setStructure(data.data.structure) - setStats(data.data.stats) - } - } catch (error) { - console.error('加载章节失败:', error) - } finally { - setLoading(false) - } - } - - const togglePart = (partId: string) => { - setExpandedParts(prev => - prev.includes(partId) - ? prev.filter(id => id !== partId) - : [...prev, partId] - ) - } - - const handleUpdatePrice = async (sectionId: string) => { - try { - const response = await fetch('/api/admin/chapters', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - action: 'updatePrice', - chapterId: sectionId, - data: { price: editPrice } - }) - }) - const result = await response.json() - if (result.success) { - alert('价格更新成功') - setEditingSection(null) - loadChapters() - } - } catch (error) { - console.error('更新价格失败:', error) - } - } - - const handleToggleFree = async (sectionId: string, currentFree: boolean) => { - try { - const response = await fetch('/api/admin/chapters', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - action: 'toggleFree', - chapterId: sectionId, - data: { isFree: !currentFree } - }) - }) - const result = await response.json() - if (result.success) { - alert('状态更新成功') - loadChapters() - } - } catch (error) { - console.error('更新状态失败:', error) - } - } - - if (loading) { - return ( -
-
加载中...
-
- ) - } - - return ( -
- {/* 导航栏 */} -
-
-
- ← 返回 -

章节管理

-
-
- - -
-
-
- -
- {/* 统计卡片 */} - {stats && ( -
-
-
{stats.totalSections}
-
总章节数
-
-
-
{stats.freeSections}
-
免费章节
-
-
-
{stats.paidSections}
-
付费章节
-
-
-
{stats.totalParts}
-
篇章数
-
-
- )} - - {/* 章节列表 */} -
- {structure.map(part => ( -
- {/* 篇标题 */} -
togglePart(part.id)} - > -
- - {part.type === 'preface' ? '📖' : - part.type === 'epilogue' ? '🎬' : - part.type === 'appendix' ? '📎' : '📚'} - - {part.title} - - ({part.chapters.reduce((acc, ch) => acc + (ch.sections?.length || 1), 0)} 节) - -
- - {expandedParts.includes(part.id) ? '▲' : '▼'} - -
- - {/* 章节内容 */} - {expandedParts.includes(part.id) && ( -
- {part.chapters.map(chapter => ( -
- {/* 章标题 */} - {chapter.sections ? ( - <> -
- {chapter.title} -
- {/* 小节列表 */} -
- {chapter.sections.map(section => ( -
-
- - {section.isFree ? '🔓' : '🔒'} - - {section.id} - {section.title} -
-
- {editingSection === section.id ? ( -
- setEditPrice(Number(e.target.value))} - className="w-20 px-2 py-1 bg-white/10 border border-white/20 rounded text-white" - min="0" - step="0.1" - /> - - -
- ) : ( - <> - - {section.isFree ? '免费' : `¥${section.price}`} - - - - - )} -
-
- ))} -
- - ) : ( -
-
- - {chapter.isFree ? '🔓' : '🔒'} - - {chapter.title} -
- - {chapter.isFree ? '免费' : `¥${chapter.price || 1}`} - -
- )} -
- ))} -
- )} -
- ))} -
-
-
- ) -} \ No newline at end of file diff --git a/next-project/app/admin/content/loading.tsx b/next-project/app/admin/content/loading.tsx deleted file mode 100644 index f15322a8..00000000 --- a/next-project/app/admin/content/loading.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Loading() { - return null -} diff --git a/next-project/app/admin/content/page.tsx b/next-project/app/admin/content/page.tsx deleted file mode 100644 index ab9eed06..00000000 --- a/next-project/app/admin/content/page.tsx +++ /dev/null @@ -1,1123 +0,0 @@ -"use client" - -import { useState, useRef, useEffect } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { Textarea } from "@/components/ui/textarea" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Badge } from "@/components/ui/badge" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" -import { bookData } from "@/lib/book-data" -import { - FileText, - BookOpen, - Settings2, - ChevronRight, - CheckCircle, - Edit3, - Save, - X, - RefreshCw, - Link2, - Download, - Upload, - Eye, - Database, - Plus, - Image as ImageIcon, - Trash2, - Search, -} from "lucide-react" - -interface EditingSection { - id: string - title: string - price: number - content?: string - filePath?: string - isNew?: boolean - partId?: string - chapterId?: string - isFree?: boolean // 是否免费章节 -} - -export default function ContentPage() { - const [expandedParts, setExpandedParts] = useState(["part-1"]) - const [editingSection, setEditingSection] = useState(null) - const [isSyncing, setIsSyncing] = useState(false) - const [isExporting, setIsExporting] = useState(false) - const [isImporting, setIsImporting] = useState(false) - const [isInitializing, setIsInitializing] = useState(false) - const [feishuDocUrl, setFeishuDocUrl] = useState("") - const [showFeishuModal, setShowFeishuModal] = useState(false) - const [showImportModal, setShowImportModal] = useState(false) - const [showNewSectionModal, setShowNewSectionModal] = useState(false) - const [importData, setImportData] = useState("") - const [isLoadingContent, setIsLoadingContent] = useState(false) - const [isSaving, setIsSaving] = useState(false) - const [searchQuery, setSearchQuery] = useState("") - const [searchResults, setSearchResults] = useState([]) - const [isSearching, setIsSearching] = useState(false) - const [uploadingImage, setUploadingImage] = useState(false) - const fileInputRef = useRef(null) - const imageInputRef = useRef(null) - - // 新建章节表单 - const [newSection, setNewSection] = useState({ - id: "", - title: "", - price: 1, - partId: "part-1", - chapterId: "chapter-1", - content: "", - }) - - const togglePart = (partId: string) => { - setExpandedParts((prev) => (prev.includes(partId) ? prev.filter((id) => id !== partId) : [...prev, partId])) - } - - const totalSections = bookData.reduce( - (sum, part) => sum + part.chapters.reduce((cSum, ch) => cSum + ch.sections.length, 0), - 0, - ) - - // 读取章节内容 - const handleReadSection = async (section: { id: string; title: string; price: number; filePath: string }) => { - setIsLoadingContent(true) - try { - const res = await fetch(`/api/db/book?action=read&id=${section.id}`) - const data = await res.json() - - if (data.success) { - setEditingSection({ - id: section.id, - title: data.section.title || section.title, - price: data.section.price || section.price, - content: data.section.content || "", - filePath: section.filePath, - }) - } else { - // 如果API失败,设置空内容 - setEditingSection({ - id: section.id, - title: section.title, - price: section.price, - content: "", - filePath: section.filePath, - }) - alert("无法读取文件内容: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Read section error:", error) - setEditingSection({ - id: section.id, - title: section.title, - price: section.price, - content: "", - filePath: section.filePath, - }) - } finally { - setIsLoadingContent(false) - } - } - - // 保存章节 - const handleSaveSection = async () => { - if (!editingSection) return - - setIsSaving(true) - try { - // 自动去掉内容中的重复标题(如# 1.2 xxx 或 # 1.4 人性的三角结构...) - let content = editingSection.content || '' - // 匹配多种格式的Markdown标题并去掉: - // 1. # 1.2 标题内容 - // 2. # 1.2 标题内容(多个空格) - // 3. ## 1.2 标题内容 - const titlePatterns = [ - new RegExp(`^#+\\s*${editingSection.id.replace('.', '\\.')}\\s+.*$`, 'gm'), // # 1.4 xxx - new RegExp(`^#+\\s*${editingSection.id.replace('.', '\\.')}[::].*$`, 'gm'), // # 1.4:xxx - new RegExp(`^#\\s+.*${editingSection.title?.slice(0, 10).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*$`, 'gm') // # xxx标题内容 - ] - for (const pattern of titlePatterns) { - content = content.replace(pattern, '') - } - content = content.replace(/^\s*\n+/, '').trim() // 去掉开头的空行 - - const res = await fetch('/api/db/book', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - id: editingSection.id, - title: editingSection.title, - price: editingSection.isFree ? 0 : editingSection.price, - content: content, - isFree: editingSection.isFree || editingSection.price === 0, - saveToFile: true, // 同时保存到文件系统 - }) - }) - - const data = await res.json() - if (data.success) { - alert(`已保存章节: ${editingSection.title}`) - setEditingSection(null) - } else { - alert("保存失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Save section error:", error) - alert("保存失败") - } finally { - setIsSaving(false) - } - } - - // 创建新章节 - const handleCreateSection = async () => { - if (!newSection.id || !newSection.title) { - alert("请填写章节ID和标题") - return - } - - setIsSaving(true) - try { - const res = await fetch('/api/db/book', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - id: newSection.id, - title: newSection.title, - price: newSection.price, - content: newSection.content, - partId: newSection.partId, - chapterId: newSection.chapterId, - saveToFile: false, // 新建章节暂不保存到文件系统 - }) - }) - - const data = await res.json() - if (data.success) { - alert(`章节创建成功: ${newSection.title}`) - setShowNewSectionModal(false) - setNewSection({ id: "", title: "", price: 1, partId: "part-1", chapterId: "chapter-1", content: "" }) - } else { - alert("创建失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Create section error:", error) - alert("创建失败") - } finally { - setIsSaving(false) - } - } - - // 上传图片 - const handleImageUpload = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (!file) return - - setUploadingImage(true) - try { - const formData = new FormData() - formData.append('file', file) - formData.append('folder', 'book-images') - - const res = await fetch('/api/upload', { - method: 'POST', - body: formData - }) - - const data = await res.json() - if (data.success) { - // 插入图片Markdown到内容 - const imageMarkdown = `![${file.name}](${data.data.url})` - if (editingSection) { - setEditingSection({ - ...editingSection, - content: (editingSection.content || '') + '\n\n' + imageMarkdown - }) - } - alert(`图片上传成功: ${data.data.url}`) - } else { - alert("上传失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Image upload error:", error) - alert("上传失败") - } finally { - setUploadingImage(false) - if (imageInputRef.current) { - imageInputRef.current.value = '' - } - } - } - - // 搜索内容 - const handleSearch = async () => { - if (!searchQuery.trim()) return - - setIsSearching(true) - try { - const res = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`) - const data = await res.json() - - if (data.success) { - setSearchResults(data.data.results || []) - } else { - alert("搜索失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Search error:", error) - alert("搜索失败") - } finally { - setIsSearching(false) - } - } - - // 同步到数据库 - const handleSyncToDatabase = async () => { - setIsSyncing(true) - try { - const res = await fetch('/api/db/book', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'sync' }) - }) - - const data = await res.json() - if (data.success) { - alert(data.message) - } else { - alert("同步失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Sync error:", error) - alert("同步失败") - } finally { - setIsSyncing(false) - } - } - - // 导出所有章节 - const handleExport = async () => { - setIsExporting(true) - try { - const res = await fetch('/api/db/book?action=export') - const blob = await res.blob() - - const url = window.URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `book_sections_${new Date().toISOString().split('T')[0]}.json` - document.body.appendChild(a) - a.click() - window.URL.revokeObjectURL(url) - document.body.removeChild(a) - - alert("导出成功") - } catch (error) { - console.error("Export error:", error) - alert("导出失败") - } finally { - setIsExporting(false) - } - } - - // 导入章节 - const handleImport = async () => { - if (!importData) { - alert("请输入或上传JSON数据") - return - } - - setIsImporting(true) - try { - const data = JSON.parse(importData) - const res = await fetch('/api/db/book', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'import', data }) - }) - - const result = await res.json() - if (result.success) { - alert(result.message) - setShowImportModal(false) - setImportData("") - } else { - alert("导入失败: " + (result.error || "未知错误")) - } - } catch (error) { - console.error("Import error:", error) - alert("导入失败: JSON格式错误") - } finally { - setIsImporting(false) - } - } - - // 文件上传 - const handleFileUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (!file) return - - const reader = new FileReader() - reader.onload = (event) => { - const content = event.target?.result as string - const fileName = file.name.toLowerCase() - - // 根据文件类型处理 - if (fileName.endsWith('.json')) { - // JSON文件直接使用 - setImportData(content) - } else if (fileName.endsWith('.txt') || fileName.endsWith('.md') || fileName.endsWith('.markdown')) { - // TXT/MD文件自动解析为JSON格式 - const parsedData = parseTxtToJson(content, file.name) - setImportData(JSON.stringify(parsedData, null, 2)) - } else { - setImportData(content) - } - } - reader.readAsText(file) - } - - // 解析TXT/MD文件为JSON格式 - const parseTxtToJson = (content: string, fileName: string) => { - const lines = content.split('\n') - const sections: any[] = [] - let currentSection: any = null - let currentContent: string[] = [] - let sectionIndex = 1 - - for (const line of lines) { - // 检测标题行(以#开头或数字+点开头) - const titleMatch = line.match(/^#+\s+(.+)$/) || line.match(/^(\d+[\.\、]\s*.+)$/) - - if (titleMatch) { - // 保存前一个章节 - if (currentSection) { - currentSection.content = currentContent.join('\n').trim() - if (currentSection.content) { - sections.push(currentSection) - } - } - - // 开始新章节 - currentSection = { - id: `import-${sectionIndex}`, - title: titleMatch[1].replace(/^#+\s*/, '').trim(), - price: 1, - is_free: sectionIndex <= 3, // 前3章免费 - } - currentContent = [] - sectionIndex++ - } else if (currentSection) { - currentContent.push(line) - } else if (line.trim()) { - // 没有标题但有内容,创建默认章节 - currentSection = { - id: `import-${sectionIndex}`, - title: fileName.replace(/\.(txt|md|markdown)$/i, ''), - price: 1, - is_free: true, - } - currentContent.push(line) - sectionIndex++ - } - } - - // 保存最后一个章节 - if (currentSection) { - currentSection.content = currentContent.join('\n').trim() - if (currentSection.content) { - sections.push(currentSection) - } - } - - return sections - } - - // 初始化数据库 - const handleInitDatabase = async () => { - if (!confirm("确定要初始化数据库吗?这将创建所有必需的表结构。")) return - - setIsInitializing(true) - try { - const res = await fetch('/api/db/init', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ adminToken: 'init_db_2025' }) - }) - const data = await res.json() - - if (data.success) { - alert(data.data?.message || '初始化成功') - } else { - alert("初始化失败: " + (data.error || "未知错误")) - } - } catch (error) { - console.error("Init database error:", error) - alert("初始化失败") - } finally { - setIsInitializing(false) - } - } - - const handleSyncFeishu = async () => { - if (!feishuDocUrl) { - alert("请输入飞书文档链接") - return - } - setIsSyncing(true) - await new Promise((resolve) => setTimeout(resolve, 2000)) - setIsSyncing(false) - setShowFeishuModal(false) - alert("飞书文档同步成功!") - } - - return ( -
-
-
-

内容管理

-

- 共 {bookData.length} 篇 · {totalSections} 节内容 -

-
-
- - - - - -
-
- - {/* 导入弹窗 */} - - - - - - 导入章节数据 - - -
-
- - - -

- • JSON格式: 直接导入章节数据
- • TXT/MD格式: 自动解析为章节内容 -

-
-
- -