From 53305757f8efc1fa4a412a773fd2677c985d5186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=8C=E5=85=88=E8=88=9C?= <2499071264@qq.com> Date: Tue, 28 Jul 2026 15:43:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=962?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + supplied-h5/.env.example | 2 + supplied-h5/src/api/supplied-service.js | 21 ++ supplied-h5/src/store/user.js | 137 +++++++ .../src/view/joyful-selection/layout.vue | 20 +- supplied-service/.env.example | 10 +- supplied-service/README.md | 8 +- supplied-service/deno.lock | 30 ++ .../netlify/functions/cart-add.ts | 2 +- supplied-service/netlify/functions/login.ts | 2 +- .../netlify/functions/order-create.ts | 2 +- .../netlify/functions/order-pay.ts | 2 +- .../netlify/functions/product-detail.ts | 2 +- .../netlify/functions/product-list.ts | 2 +- .../netlify/functions/supplier-info.ts | 38 ++ .../netlify/functions/supplier-token.ts | 86 +++++ supplied-service/netlify/functions/upload.ts | 2 +- .../netlify/functions/user-info.ts | 2 +- supplied-service/netlify/utils/crypto.ts | 9 + supplied-service/netlify/utils/handler.ts | 2 +- supplied-service/netlify/utils/http.ts | 16 +- supplied-service/netlify/utils/prisma.ts | 11 +- supplied-service/package-lock.json | 335 +++++++++++++++++- supplied-service/package.json | 4 +- supplied-service/prisma.config.ts | 2 +- supplied-service/prisma/schema.prisma | 20 +- 26 files changed, 734 insertions(+), 36 deletions(-) create mode 100644 supplied-h5/.env.example create mode 100644 supplied-h5/src/api/supplied-service.js create mode 100644 supplied-h5/src/store/user.js create mode 100644 supplied-service/deno.lock create mode 100644 supplied-service/netlify/functions/supplier-info.ts create mode 100644 supplied-service/netlify/functions/supplier-token.ts create mode 100644 supplied-service/netlify/utils/crypto.ts diff --git a/.gitignore b/.gitignore index 85676bc..67f5a95 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ dist hs_err_pid* replay_pid* + +# Local Netlify folder +.netlify diff --git a/supplied-h5/.env.example b/supplied-h5/.env.example new file mode 100644 index 0000000..2bea682 --- /dev/null +++ b/supplied-h5/.env.example @@ -0,0 +1,2 @@ +VUE_APP_SUPPLIED_API_BASE="http://localhost:8888/.netlify/functions" +VUE_APP_SUPPLIER_IDENTIFICATION="joyful-selection" diff --git a/supplied-h5/src/api/supplied-service.js b/supplied-h5/src/api/supplied-service.js new file mode 100644 index 0000000..c8ced21 --- /dev/null +++ b/supplied-h5/src/api/supplied-service.js @@ -0,0 +1,21 @@ +const API_BASE = process.env.VUE_APP_SUPPLIED_API_BASE || 'http://localhost:8888/.netlify/functions' + +const normalizeBase = base => base.replace(/\/$/, '') + +export const postFunction = async (name, body = {}) => { + const response = await fetch(`${normalizeBase(API_BASE)}/${name}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }) + + const payload = await response.json().catch(() => ({})) + if (!response.ok || payload.success === false) { + const message = payload.error?.message || payload.msg || '请求失败' + throw new Error(message) + } + + return payload.data +} diff --git a/supplied-h5/src/store/user.js b/supplied-h5/src/store/user.js new file mode 100644 index 0000000..a8c804c --- /dev/null +++ b/supplied-h5/src/store/user.js @@ -0,0 +1,137 @@ +import { defineStore } from 'pinia' +import { postFunction } from '@/api/supplied-service' + +const USER_KEY = 'joyful-selection-user' +const DEFAULT_SUPPLIER_IDENTIFICATION = (process.env.VUE_APP_SUPPLIER_IDENTIFICATION || '').toLowerCase() + +const readUser = () => { + try { + return JSON.parse(localStorage.getItem(USER_KEY) || '{}') + } catch (error) { + return {} + } +} + +const saveUser = data => { + localStorage.setItem(USER_KEY, JSON.stringify(data)) +} + +export const useUserStore = defineStore('joyfulSelectionUser', { + state: () => ({ + userId: '', + supplierIdentification: '', + supplierId: '', + supplierName: '', + clientId: '', + token: '', + refreshToken: '', + tokenExpiresAt: 0, + loaded: false, + loadingToken: false + }), + getters: { + hasToken: state => Boolean(state.token && state.tokenExpiresAt > Date.now()) + }, + actions: { + loadUser() { + const data = readUser() + this.userId = data.userId || '' + const supplierIdentification = DEFAULT_SUPPLIER_IDENTIFICATION || data.supplierIdentification || '' + const supplierChanged = Boolean(data.supplierIdentification && supplierIdentification !== data.supplierIdentification) + + this.supplierIdentification = supplierIdentification + this.supplierId = supplierChanged ? '' : data.supplierId || '' + this.supplierName = supplierChanged ? '' : data.supplierName || '' + this.clientId = supplierChanged ? '' : data.clientId || '' + this.token = supplierChanged ? '' : data.token || '' + this.refreshToken = supplierChanged ? '' : data.refreshToken || '' + this.tokenExpiresAt = supplierChanged ? 0 : Number(data.tokenExpiresAt || 0) + this.loaded = true + }, + persistUser() { + saveUser({ + userId: this.userId, + supplierIdentification: this.supplierIdentification, + supplierId: this.supplierId, + supplierName: this.supplierName, + clientId: this.clientId, + token: this.token, + refreshToken: this.refreshToken, + tokenExpiresAt: this.tokenExpiresAt + }) + this.loaded = true + }, + hydrateFromRoute(query = {}) { + if (!this.loaded) { + this.loadUser() + } + + const nextUserId = query.userId || '' + + if (nextUserId && nextUserId !== this.userId) { + this.userId = nextUserId + this.token = '' + this.refreshToken = '' + this.tokenExpiresAt = 0 + } + if (!this.supplierIdentification) { + this.supplierIdentification = DEFAULT_SUPPLIER_IDENTIFICATION + } + + this.persistUser() + }, + async fetchSupplierInfo() { + if (!this.loaded) { + this.loadUser() + } + if (!this.supplierIdentification) { + return + } + + const data = await postFunction('supplier-info', { + supplierIdentification: this.supplierIdentification + }) + + if (data?.supplierId && data.supplierId !== this.supplierId) { + this.token = '' + this.refreshToken = '' + this.tokenExpiresAt = 0 + } + + this.supplierId = data?.supplierId || '' + this.supplierName = data?.supplierName || '' + this.clientId = data?.clientId || '' + this.persistUser() + }, + async fetchBridgeToken() { + if (!this.loaded) { + this.loadUser() + } + if (!this.userId || !this.supplierIdentification || this.loadingToken || this.hasToken) { + return + } + + this.loadingToken = true + try { + if (!this.supplierId) { + await this.fetchSupplierInfo() + } + + const data = await postFunction('supplier-token', { + userId: this.userId, + supplierIdentification: this.supplierIdentification + }) + + this.supplierId = data?.supplierId || this.supplierId + this.supplierName = data?.supplierName || this.supplierName + this.token = data?.token || data?.accessToken || '' + this.refreshToken = data?.refreshToken || '' + const expiresIn = Number(data?.expiresIn || 0) + this.tokenExpiresAt = this.token && expiresIn > 0 ? Date.now() + expiresIn * 1000 : 0 + this.persistUser() + } finally { + this.loadingToken = false + } + } + } +}) diff --git a/supplied-h5/src/view/joyful-selection/layout.vue b/supplied-h5/src/view/joyful-selection/layout.vue index 26694d3..381062a 100644 --- a/supplied-h5/src/view/joyful-selection/layout.vue +++ b/supplied-h5/src/view/joyful-selection/layout.vue @@ -13,6 +13,7 @@ diff --git a/supplied-service/.env.example b/supplied-service/.env.example index 6427f1b..831742f 100644 --- a/supplied-service/.env.example +++ b/supplied-service/.env.example @@ -1,9 +1,9 @@ -# Pooled Neon connection string for Netlify Functions runtime. -DATABASE_URL="postgresql://USER:PASSWORD@HOST-pooler.REGION.aws.neon.tech/DB?sslmode=require" - -# Direct Neon connection string for Prisma CLI commands such as migrate. -DIRECT_URL="postgresql://USER:PASSWORD@HOST.REGION.aws.neon.tech/DB?sslmode=require" +# MySQL connection string for Netlify Functions runtime and Prisma CLI. +DATABASE_URL="mysql://supper:Supper%40123456@localhost:10086/supplier_db" # Optional app settings. JWT_SECRET="replace-me" UPLOAD_TOKEN="replace-me" + +# Main points mall bridge API, for example http://localhost/open. +POINTS_MALL_OPEN_API_BASE="http://localhost/open" diff --git a/supplied-service/README.md b/supplied-service/README.md index 4845591..5ad8e43 100644 --- a/supplied-service/README.md +++ b/supplied-service/README.md @@ -25,10 +25,12 @@ prisma/ Copy `.env.example` to `.env` and set: -- `DATABASE_URL`: pooled Neon connection string for Netlify Functions runtime -- `DIRECT_URL`: direct Neon connection string for Prisma CLI and migrations +- `DATABASE_URL`: Neon Postgres connection string for Netlify Functions runtime and Prisma CLI - `JWT_SECRET`: auth signing secret - `UPLOAD_TOKEN`: upload guard token +- `POINTS_MALL_OPEN_API_BASE`: main platform bridge base URL, for example `http://localhost/open` + +Supplier H5 uses `VUE_APP_SUPPLIER_IDENTIFICATION` to request supplier config from this service. The supplier `clientSecret` stays in the database and is only used by the Netlify Function when requesting the platform bridge token. ## Scripts @@ -55,3 +57,5 @@ Current placeholders: - `order-pay` - `user-info` - `upload` +- `supplier-info` +- `supplier-token` diff --git a/supplied-service/deno.lock b/supplied-service/deno.lock new file mode 100644 index 0000000..fb17491 --- /dev/null +++ b/supplied-service/deno.lock @@ -0,0 +1,30 @@ +{ + "version": "5", + "remote": { + "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb", + "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025", + "https://edge.netlify.com/bootstrap/context.ts": "e97240232121e2f369f6546ce961490f34d961ea1ea54be3ff09633e3f08373f", + "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05", + "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39", + "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f" + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@neondatabase/serverless@^1.0.1", + "npm:@netlify/functions@^4.2.5", + "npm:@prisma/adapter-neon@^6.16.0", + "npm:@prisma/client@^6.16.0", + "npm:@types/crypto-js@^4.2.2", + "npm:@types/node@^24.10.1", + "npm:@types/ws@^8.18.1", + "npm:crypto-js@^4.2.0", + "npm:dotenv@^17.2.3", + "npm:netlify-cli@^23.13.3", + "npm:prisma@^6.19.3", + "npm:typescript@^5.9.3", + "npm:ws@^8.18.3" + ] + } + } +} diff --git a/supplied-service/netlify/functions/cart-add.ts b/supplied-service/netlify/functions/cart-add.ts index b3618d4..d084907 100644 --- a/supplied-service/netlify/functions/cart-add.ts +++ b/supplied-service/netlify/functions/cart-add.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['POST']) if (methodResponse) return methodResponse - return ok({ message: 'cart-add endpoint placeholder' }) + return ok({ message: '加入购物车接口待实现' }) }) diff --git a/supplied-service/netlify/functions/login.ts b/supplied-service/netlify/functions/login.ts index 3bf0cbc..532e78c 100644 --- a/supplied-service/netlify/functions/login.ts +++ b/supplied-service/netlify/functions/login.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['POST']) if (methodResponse) return methodResponse - return ok({ message: 'login endpoint placeholder' }) + return ok({ message: '登录接口待实现' }) }) diff --git a/supplied-service/netlify/functions/order-create.ts b/supplied-service/netlify/functions/order-create.ts index 3de2734..01770d9 100644 --- a/supplied-service/netlify/functions/order-create.ts +++ b/supplied-service/netlify/functions/order-create.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['POST']) if (methodResponse) return methodResponse - return ok({ message: 'order-create endpoint placeholder' }) + return ok({ message: '创建订单接口待实现' }) }) diff --git a/supplied-service/netlify/functions/order-pay.ts b/supplied-service/netlify/functions/order-pay.ts index 6ae7f34..24eedde 100644 --- a/supplied-service/netlify/functions/order-pay.ts +++ b/supplied-service/netlify/functions/order-pay.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['POST']) if (methodResponse) return methodResponse - return ok({ message: 'order-pay endpoint placeholder' }) + return ok({ message: '订单支付接口待实现' }) }) diff --git a/supplied-service/netlify/functions/product-detail.ts b/supplied-service/netlify/functions/product-detail.ts index ea7a90b..f936c23 100644 --- a/supplied-service/netlify/functions/product-detail.ts +++ b/supplied-service/netlify/functions/product-detail.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['GET']) if (methodResponse) return methodResponse - return ok({ message: 'product-detail endpoint placeholder' }) + return ok({ message: '商品详情接口待实现' }) }) diff --git a/supplied-service/netlify/functions/product-list.ts b/supplied-service/netlify/functions/product-list.ts index a9c425f..0bdc2af 100644 --- a/supplied-service/netlify/functions/product-list.ts +++ b/supplied-service/netlify/functions/product-list.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['GET']) if (methodResponse) return methodResponse - return ok({ message: 'product-list endpoint placeholder' }) + return ok({ message: '商品列表接口待实现' }) }) diff --git a/supplied-service/netlify/functions/supplier-info.ts b/supplied-service/netlify/functions/supplier-info.ts new file mode 100644 index 0000000..b5b9c14 --- /dev/null +++ b/supplied-service/netlify/functions/supplier-info.ts @@ -0,0 +1,38 @@ +import type { Context } from '@netlify/functions' +import { guardMethod } from '../utils/method.js' +import { fail, ok } from '../utils/http.js' +import { readJson } from '../utils/request.js' +import { prisma } from '../utils/prisma.js' +import { withErrorBoundary } from '../utils/handler.js' + +type SupplierInfoRequest = { + supplierIdentification?: string +} + +export default withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() + + if (!supplierIdentification) { + return fail('供应商唯一标识不能为空') + } + + const supplier = await prisma.supplierInfo.findUnique({ + where: { supplierIdentification } + }) + + if (!supplier || supplier.deleted === 1) { + return fail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') + } + + return ok({ + id: supplier.id, + supplierIdentification: supplier.supplierIdentification, + supplierId: supplier.supplierId, + supplierName: supplier.supplierName, + clientId: supplier.clientId + }) +}) diff --git a/supplied-service/netlify/functions/supplier-token.ts b/supplied-service/netlify/functions/supplier-token.ts new file mode 100644 index 0000000..0f15467 --- /dev/null +++ b/supplied-service/netlify/functions/supplier-token.ts @@ -0,0 +1,86 @@ +import type { Context } from '@netlify/functions' +import { guardMethod } from '../utils/method.js' +import { fail, ok } from '../utils/http.js' +import { readJson } from '../utils/request.js' +import { prisma } from '../utils/prisma.js' +import { createBridgeSignature } from '../utils/crypto.js' +import { withErrorBoundary } from '../utils/handler.js' + +type SupplierTokenRequest = { + userId?: string + supplierIdentification?: string +} + +type BridgeTokenResponse = { + resultCode?: number + message?: string + accessToken?: string + refreshToken?: string + expire?: number + refreshExpire?: number + time?: string +} + +const normalizeBase = (base: string) => base.replace(/\/$/, '') + +export default withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const userId = String(body.userId || '').trim() + const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() + + if (!userId || !supplierIdentification) { + return fail('用户信息和供应商唯一标识不能为空') + } + + const supplier = await prisma.supplierInfo.findUnique({ + where: { supplierIdentification } + }) + + if (!supplier || supplier.deleted === 1) { + return fail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') + } + + const timestamp = String(Date.now()) + const signature = createBridgeSignature(supplier.clientId, supplier.clientSecret, timestamp) + const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '') + + if (!apiBase) { + return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING') + } + + const response = await fetch(`${apiBase}/connect/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + supplierId: supplier.supplierId, + clientId: supplier.clientId, + clientSecret: signature, + code: '', + timestamp + }) + }) + + const tokenPayload = (await response.json().catch(() => ({}))) as BridgeTokenResponse + + if (!response.ok || tokenPayload.resultCode !== 0) { + return fail(tokenPayload.message || '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED') + } + + return ok({ + supplierIdentification, + supplierId: supplier.supplierId, + supplierName: supplier.supplierName, + userId, + token: tokenPayload.accessToken, + accessToken: tokenPayload.accessToken, + refreshToken: tokenPayload.refreshToken, + expiresIn: tokenPayload.expire, + refreshExpiresIn: tokenPayload.refreshExpire, + issuedAt: tokenPayload.time + }) +}) diff --git a/supplied-service/netlify/functions/upload.ts b/supplied-service/netlify/functions/upload.ts index cbe9b9f..ab74663 100644 --- a/supplied-service/netlify/functions/upload.ts +++ b/supplied-service/netlify/functions/upload.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['POST']) if (methodResponse) return methodResponse - return ok({ message: 'upload endpoint placeholder' }) + return ok({ message: '上传接口待实现' }) }) diff --git a/supplied-service/netlify/functions/user-info.ts b/supplied-service/netlify/functions/user-info.ts index 390c722..1da9459 100644 --- a/supplied-service/netlify/functions/user-info.ts +++ b/supplied-service/netlify/functions/user-info.ts @@ -7,5 +7,5 @@ export default withErrorBoundary(async (request: Request, _context: Context) => const methodResponse = guardMethod(request, ['GET']) if (methodResponse) return methodResponse - return ok({ message: 'user-info endpoint placeholder' }) + return ok({ message: '用户信息接口待实现' }) }) diff --git a/supplied-service/netlify/utils/crypto.ts b/supplied-service/netlify/utils/crypto.ts new file mode 100644 index 0000000..c29662e --- /dev/null +++ b/supplied-service/netlify/utils/crypto.ts @@ -0,0 +1,9 @@ +import CryptoJS from 'crypto-js' + +export const sha256 = (value: string) => { + return CryptoJS.SHA256(value).toString(CryptoJS.enc.Hex) +} + +export const createBridgeSignature = (clientId: string, clientSecret: string, timestamp: string) => { + return sha256(`${clientId}${clientSecret}${timestamp}${clientSecret}`) +} diff --git a/supplied-service/netlify/utils/handler.ts b/supplied-service/netlify/utils/handler.ts index ff1bba5..d16b0a9 100644 --- a/supplied-service/netlify/utils/handler.ts +++ b/supplied-service/netlify/utils/handler.ts @@ -9,7 +9,7 @@ export const withErrorBoundary = (handler: FunctionHandler): FunctionHandler => return await handler(request, context) } catch (error) { console.error(error) - return fail('Internal server error', 500, 'INTERNAL_SERVER_ERROR') + return fail('服务异常,请稍后再试', 500, 'INTERNAL_SERVER_ERROR') } } } diff --git a/supplied-service/netlify/utils/http.ts b/supplied-service/netlify/utils/http.ts index 45cda93..0b0406e 100644 --- a/supplied-service/netlify/utils/http.ts +++ b/supplied-service/netlify/utils/http.ts @@ -1,10 +1,17 @@ export type JsonBody = Record | unknown[] +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS' +} + export const json = (body: JsonBody, init: ResponseInit = {}) => Response.json(body, { ...init, headers: { 'Content-Type': 'application/json', + ...corsHeaders, ...(init.headers || {}) } }) @@ -16,10 +23,11 @@ export const fail = (message: string, status = 400, code = 'BAD_REQUEST') => export const methodNotAllowed = (allowedMethods: string[]) => json( - { success: false, error: { code: 'METHOD_NOT_ALLOWED', message: 'Method not allowed' } }, + { success: false, error: { code: 'METHOD_NOT_ALLOWED', message: '请求方式不支持' } }, { status: 405, headers: { + ...corsHeaders, Allow: allowedMethods.join(', ') } } @@ -28,9 +36,5 @@ export const methodNotAllowed = (allowedMethods: string[]) => export const handleOptions = () => new Response(null, { status: 204, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS' - } + headers: corsHeaders }) diff --git a/supplied-service/netlify/utils/prisma.ts b/supplied-service/netlify/utils/prisma.ts index 28332b0..7a99582 100644 --- a/supplied-service/netlify/utils/prisma.ts +++ b/supplied-service/netlify/utils/prisma.ts @@ -1,23 +1,16 @@ -import { neonConfig } from '@neondatabase/serverless' -import { PrismaNeon } from '@prisma/adapter-neon' import { PrismaClient } from '@prisma/client' -import ws from 'ws' - -neonConfig.webSocketConstructor = ws const connectionString = process.env.DATABASE_URL if (!connectionString) { - throw new Error('DATABASE_URL is required') + throw new Error('缺少数据库连接配置 DATABASE_URL') } const globalForPrisma = globalThis as typeof globalThis & { prisma?: PrismaClient } -const adapter = new PrismaNeon({ connectionString }) - -export const prisma = globalForPrisma.prisma || new PrismaClient({ adapter }) +export const prisma = globalForPrisma.prisma || new PrismaClient() if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma diff --git a/supplied-service/package-lock.json b/supplied-service/package-lock.json index b58a299..1843a52 100644 --- a/supplied-service/package-lock.json +++ b/supplied-service/package-lock.json @@ -12,14 +12,16 @@ "@netlify/functions": "^4.2.5", "@prisma/adapter-neon": "^6.16.0", "@prisma/client": "^6.16.0", + "crypto-js": "^4.2.0", "dotenv": "^17.2.3", "ws": "^8.18.3" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/node": "^24.10.1", "@types/ws": "^8.18.1", "netlify-cli": "^23.13.3", - "prisma": "^6.16.0", + "prisma": "^6.19.3", "typescript": "^5.9.3" } }, @@ -967,6 +969,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -2116,6 +2125,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -9260,6 +9275,270 @@ } } }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", + "cpu": [ + "arm" + ], + "extraneous": true, + "os": [ + "android" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "android" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "darwin" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "darwin" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "freebsd" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "freebsd" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", + "cpu": [ + "arm" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", + "cpu": [ + "arm" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", + "cpu": [ + "loong64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", + "cpu": [ + "ppc64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", + "cpu": [ + "riscv64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", + "cpu": [ + "riscv64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", + "cpu": [ + "s390x" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "linux" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "openharmony" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "os": [ + "win32" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", + "cpu": [ + "ia32" + ], + "extraneous": true, + "os": [ + "win32" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "win32" + ] + }, + "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", + "cpu": [ + "x64" + ], + "extraneous": true, + "os": [ + "win32" + ] + }, "node_modules/netlify-cli/node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -13324,6 +13603,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/netlify-cli/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "extraneous": true, + "hasInstallScript": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/netlify-cli/node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -17381,6 +17673,47 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, + "node_modules/netlify-cli/node_modules/rollup": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", + "extraneous": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", + "fsevents": "~2.3.2" + } + }, "node_modules/netlify-cli/node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/supplied-service/package.json b/supplied-service/package.json index c1423e6..ae01753 100644 --- a/supplied-service/package.json +++ b/supplied-service/package.json @@ -16,14 +16,16 @@ "@netlify/functions": "^4.2.5", "@prisma/adapter-neon": "^6.16.0", "@prisma/client": "^6.16.0", + "crypto-js": "^4.2.0", "dotenv": "^17.2.3", "ws": "^8.18.3" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/node": "^24.10.1", "@types/ws": "^8.18.1", "netlify-cli": "^23.13.3", - "prisma": "^6.16.0", + "prisma": "^6.19.3", "typescript": "^5.9.3" } } diff --git a/supplied-service/prisma.config.ts b/supplied-service/prisma.config.ts index 03a7de4..ccffac9 100644 --- a/supplied-service/prisma.config.ts +++ b/supplied-service/prisma.config.ts @@ -4,6 +4,6 @@ import { defineConfig, env } from 'prisma/config' export default defineConfig({ schema: 'prisma/schema.prisma', datasource: { - url: env('DIRECT_URL') + url: env('DATABASE_URL') } }) diff --git a/supplied-service/prisma/schema.prisma b/supplied-service/prisma/schema.prisma index 7306394..712486f 100644 --- a/supplied-service/prisma/schema.prisma +++ b/supplied-service/prisma/schema.prisma @@ -3,7 +3,8 @@ generator client { } datasource db { - provider = "postgresql" + provider = "mysql" + url = env("DATABASE_URL") } model User { @@ -37,6 +38,23 @@ model Product { orderItems OrderItem[] } +model SupplierInfo { + id Int @id @default(autoincrement()) + supplierIdentification String @unique + supplierId String @unique + supplierName String + clientId String + clientSecret String + createTime DateTime @default(now()) + updateTime DateTime @updatedAt + creator String? + updator String? + version Int @default(0) + deleted Int @default(0) + + @@map("supplier_info") +} + model CartItem { id String @id @default(cuid()) userId String