From 813c733860f3eed1915c4dd338a3d255a303449c Mon Sep 17 00:00:00 2001 From: yml Date: Tue, 14 Apr 2026 18:07:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development.example | 1 - .env.production.example | 1 - .env.服务端 | 24 +++++++++ apps/backend/README.md | 1 - apps/backend/config/default.cjs | 1 - apps/backend/src/config/runtime.js | 5 -- apps/backend/src/index.js | 5 +- apps/backend/src/services/session/session.js | 52 ++++++++++++++++++- .../src/services/session/session.test.js | 25 +++++++++ apps/backend/src/types/runtime-config.js | 1 - 10 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 .env.服务端 create mode 100644 apps/backend/src/services/session/session.test.js diff --git a/.env.development.example b/.env.development.example index dde86db2..d7f6a569 100644 --- a/.env.development.example +++ b/.env.development.example @@ -25,4 +25,3 @@ ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"dev-admin-123456","rol AGISO_APP_SECRET= AGISO_MESSAGING_ENABLED=false -AGISO_MESSAGE_APP_SECRET= diff --git a/.env.production.example b/.env.production.example index 0d60c5fd..ae23b720 100644 --- a/.env.production.example +++ b/.env.production.example @@ -22,4 +22,3 @@ ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"replace-with-strong-ad AGISO_APP_SECRET= AGISO_MESSAGING_ENABLED=false -AGISO_MESSAGE_APP_SECRET= diff --git a/.env.服务端 b/.env.服务端 new file mode 100644 index 00000000..424fd96b --- /dev/null +++ b/.env.服务端 @@ -0,0 +1,24 @@ +APP_DOMAIN=order.khhao.com +APP_BASE_URL=https://order.khhao.com + +TZ=Asia/Shanghai +BACKEND_PORT=3000 +CLAIM_BASE_URL=https://order.khhao.com/#/claim +DATABASE_URL=postgres://postgres:FACCqheAzy51op0RFdbv@postgres:5432/order_site +DATABASE_SSL=false +DATABASE_MAX_CONNECTIONS=20 +DATA_ROOT=/app/data +OCR_PROJECT_ROOT=/app/subservices/ocr-worker +TENCENT_REDEEM_PROOF_MODE=basic +TENCENT_BROWSER_HEADLESS=true +TENCENT_BROWSER_DEVTOOLS=false +TENCENT_BROWSER_PREWARM=true +TENCENT_BROWSER_KEEP_ALIVE=true +TENCENT_BROWSER_SLOW_MO=0 +TENCENT_SESSION_DEBUG=false + +ADMIN_SESSION_SECRET=xm4BDpbdHJvU4RIlpGqD0nvQTZtIidvQMk6LSP1d +ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"Mzl5iG6pkdrzLs73COiB","role":"admin"},{"username":"operator","password":"d9tvmbPPMRdLySEhORzk","role":"operator"}] + +AGISO_APP_SECRET=9fcb0f688aab713f9af3e85d29ba8b15 +AGISO_MESSAGING_ENABLED=false diff --git a/apps/backend/README.md b/apps/backend/README.md index 8d690eda..7a7e529f 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -76,7 +76,6 @@ npm run typecheck - 全局兜底配置仍走 `.env`: - `AGISO_APP_SECRET` - `AGISO_MESSAGING_ENABLED` - - `AGISO_MESSAGE_APP_SECRET` - 默认消息模板与店铺级覆盖配置改为文件: - `apps/backend/data/agiso-shops.json` - 后台维护入口: diff --git a/apps/backend/config/default.cjs b/apps/backend/config/default.cjs index 104dc506..b165617d 100644 --- a/apps/backend/config/default.cjs +++ b/apps/backend/config/default.cjs @@ -60,7 +60,6 @@ module.exports = { enabled: false, sendMessageEndpoint: 'https://gw-api.agiso.com/aldsIdle/ImMsg/SendMsg', apiVersion: '1', - appSecret: '', messageTemplate: '您的订单 {platformOrderId} 已创建领取链接,请在 {expiredAt} 前完成领取:{claimUrl}', autoDeliveryMessageTemplate: '您的订单 {platformOrderId} 已完成自动发货,请注意查收。', shops: {}, diff --git a/apps/backend/src/config/runtime.js b/apps/backend/src/config/runtime.js index c63e96a6..7d917b96 100644 --- a/apps/backend/src/config/runtime.js +++ b/apps/backend/src/config/runtime.js @@ -215,11 +215,6 @@ function applyEnvOverrides(baseConfig) { nextConfig.platforms.agiso.autoDelivery.apiVersion = agisoAutoDeliveryApiVersion } - const agisoMessageAppSecret = String(process.env.AGISO_MESSAGE_APP_SECRET || '').trim() - if (agisoMessageAppSecret) { - nextConfig.platforms.agiso.messaging.appSecret = agisoMessageAppSecret - } - const agisoMessageApiVersion = String(process.env.AGISO_MESSAGE_API_VERSION || '').trim() if (agisoMessageApiVersion) { nextConfig.platforms.agiso.messaging.apiVersion = agisoMessageApiVersion diff --git a/apps/backend/src/index.js b/apps/backend/src/index.js index c0bf2617..e2f9b899 100644 --- a/apps/backend/src/index.js +++ b/apps/backend/src/index.js @@ -18,6 +18,7 @@ const CORE_BOOT_RETRY_DELAY_MS = 5_000 const app = express() const port = Number(runtimeConfig.server.port || 3000) +const host = '0.0.0.0' const startupState = createStartupState() app.use((req, res, next) => { @@ -88,8 +89,8 @@ app.use('/api/v1/webhooks', webhooksRouter) app.use('/api/v1/claim', claimsRouter) app.use('/api/v1/admin', adminRouter) -const server = app.listen(port, () => { - logInfo('[startup]', `order-site-backend listening on http://127.0.0.1:${port}`) +const server = app.listen(port, host, () => { + logInfo('[startup]', `order-site-backend listening on http://${host}:${port}`) void bootstrapCoreServices() }) diff --git a/apps/backend/src/services/session/session.js b/apps/backend/src/services/session/session.js index 8fc9eb31..6a621d8d 100644 --- a/apps/backend/src/services/session/session.js +++ b/apps/backend/src/services/session/session.js @@ -41,6 +41,7 @@ const ACTIVITY_URL = 'https://df.qq.com/cp/a20240812cdk/index.html' const CHROME_PATH = String(runtimeConfig.browser.chromePath || '').trim() const DEFAULT_LOGIN_TYPE = 'qq' const SESSION_TTL_MS = 30 * 60 * 1000 +export const REDEEM_SUCCESS_AUTO_CLOSE_MS = 3 * 60 * 1000 const DEFAULT_VIEWPORT = resolveDefaultViewport() const SESSION_DEBUG_ENABLED = Boolean(runtimeConfig.session.debug) @@ -103,6 +104,8 @@ export async function createTencentBrowserSession(payload = {}) { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), expiresAt: Date.now() + SESSION_TTL_MS, + autoCloseAt: 0, + autoCloseTimer: null, sessionDir, browserContext, page, @@ -245,7 +248,7 @@ export async function refreshTencentBrowserSession(sessionId, { includeQrImage = } session.updatedAt = new Date().toISOString() - session.expiresAt = Date.now() + SESSION_TTL_MS + session.expiresAt = resolveTencentSessionExpiresAt(session) if (!session.qrImageBase64 || session.status === 'expired') { if (session.status === 'expired') { @@ -293,7 +296,9 @@ export async function redeemTencentBrowserSession(sessionId, payload = {}) { }) } - return runTencentBrowserRedeem({ + resetTencentSessionAutoClose(session) + + await runTencentBrowserRedeem({ session, code, maxAttempts, @@ -312,6 +317,14 @@ export async function redeemTencentBrowserSession(sessionId, payload = {}) { saveRedeemArtifacts, activityUrl: ACTIVITY_URL, }) + + armRedeemedSessionAutoClose(session) + await persistSessionState(session) + + return { + ...buildSessionPayload(session), + redeem: session.lastRedeem, + } } export async function closeTencentBrowserSession(sessionId) { @@ -611,12 +624,46 @@ async function ensureLoginTab(page, loginType) { } function applyInitialQrSessionState(session) { + resetTencentSessionAutoClose(session) session.status = 'waiting_scan' session.notice = `请使用${getLoginTypeLabel(session.loginType)}扫码登录` session.updatedAt = new Date().toISOString() session.expiresAt = Date.now() + SESSION_TTL_MS } +export function resolveTencentSessionExpiresAt(session, nowMs = Date.now()) { + const autoCloseAt = Number(session?.autoCloseAt || 0) + if (autoCloseAt > 0) { + return autoCloseAt + } + + return nowMs + SESSION_TTL_MS +} + +function armRedeemedSessionAutoClose(session) { + resetTencentSessionAutoClose(session) + + const autoCloseAt = Date.now() + REDEEM_SUCCESS_AUTO_CLOSE_MS + session.autoCloseAt = autoCloseAt + session.expiresAt = autoCloseAt + session.autoCloseTimer = setTimeout(() => { + void closeSession(session, { markClosed: true }).catch(() => { + // ignore delayed auto-close failures + }) + }, REDEEM_SUCCESS_AUTO_CLOSE_MS) +} + +function resetTencentSessionAutoClose(session) { + if (session?.autoCloseTimer) { + clearTimeout(session.autoCloseTimer) + session.autoCloseTimer = null + } + + if (session) { + session.autoCloseAt = 0 + } +} + async function extractHostState(page) { return page.evaluate(() => { const isVisible = (element) => { @@ -924,6 +971,7 @@ async function cleanupExpiredTencentBrowserSessions() { } async function closeSession(session, { markClosed }) { + resetTencentSessionAutoClose(session) sessions.delete(session.sessionId) try { diff --git a/apps/backend/src/services/session/session.test.js b/apps/backend/src/services/session/session.test.js new file mode 100644 index 00000000..b7cb40b2 --- /dev/null +++ b/apps/backend/src/services/session/session.test.js @@ -0,0 +1,25 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + REDEEM_SUCCESS_AUTO_CLOSE_MS, + resolveTencentSessionExpiresAt, +} from './session.js' + +test('resolveTencentSessionExpiresAt uses normal ttl when no auto-close is armed', () => { + const nowMs = Date.UTC(2026, 3, 14, 9, 30, 0) + assert.equal( + resolveTencentSessionExpiresAt({ autoCloseAt: 0 }, nowMs), + nowMs + (30 * 60 * 1000), + ) +}) + +test('resolveTencentSessionExpiresAt keeps redeemed auto-close deadline once armed', () => { + const nowMs = Date.UTC(2026, 3, 14, 9, 30, 0) + const autoCloseAt = nowMs + REDEEM_SUCCESS_AUTO_CLOSE_MS + + assert.equal( + resolveTencentSessionExpiresAt({ autoCloseAt }, nowMs + 60_000), + autoCloseAt, + ) +}) diff --git a/apps/backend/src/types/runtime-config.js b/apps/backend/src/types/runtime-config.js index 1d075ea3..1b65551c 100644 --- a/apps/backend/src/types/runtime-config.js +++ b/apps/backend/src/types/runtime-config.js @@ -79,7 +79,6 @@ export {} * } * messaging: { * enabled: boolean - * appSecret: string * apiVersion: string * sendMessageEndpoint: string * messageTemplate: string