模拟第三方信息存入数据库并进行消息推送

This commit is contained in:
not-like-juvenile
2026-03-09 17:27:56 +08:00
parent 436b7b251f
commit ee9fabd806
5 changed files with 413 additions and 29 deletions

View File

@@ -161,15 +161,29 @@ async function start() {
async function claimNotification(id) {
try {
const body = { status_code: 'processing', updated_at: new Date().toISOString() }
// 仅当当前仍为 pending 或 retrying 且到期时,才抢占
// 仅当当前仍为 pending 或 retrying 且到期时,才抢占
// 为了避免在 PATCH 中使用复杂 or= 逻辑树导致匹配失败,这里拆成两次尝试。
const now = new Date().toISOString()
const path = `express_notifications?id=eq.${encodeURIComponent(id)}&or=(status_code.is.null,and(status_code.eq.retrying,or(next_attempt_at.is.null,next_attempt_at.lte.${encodeURIComponent(now)})))`
const resp = await supaFetch(path, { method: 'PATCH', headers: { 'Content-Type': 'application/json', Prefer: 'return=representation' }, body: JSON.stringify(body) })
if (!resp.ok) return null
const j = await resp.json().catch(() => null)
if (Array.isArray(j)) return j.length > 0 ? j[0] : null
if (!j || !j.id) return null
return j
const attempts = [
// pending: status_code IS NULL
`express_notifications?id=eq.${encodeURIComponent(id)}&status_code=is.null`,
// retrying: status_code='retrying' and next_attempt_at is null or <= now
`express_notifications?id=eq.${encodeURIComponent(id)}&status_code=eq.retrying&or=(next_attempt_at.is.null,next_attempt_at.lte.${encodeURIComponent(now)})`
]
for (const path of attempts) {
const resp = await supaFetch(path, { method: 'PATCH', headers: { 'Content-Type': 'application/json', Prefer: 'return=representation' }, body: JSON.stringify(body) })
if (!resp.ok) continue
const j = await resp.json().catch(() => null)
if (Array.isArray(j)) {
if (j.length > 0) return j[0]
// debug hint: matched 0 rows
try { console.log('claimNotification: updated 0 rows for', path) } catch (e) {}
continue
}
if (j && j.id) return j
}
return null
} catch (e) {
console.warn('claimNotification error', e)
return null
@@ -192,7 +206,17 @@ async function start() {
const txt = await resp.text().catch(() => '')
let json
try { json = JSON.parse(txt) } catch (e) { json = { statusText: txt } }
return { ok: resp.ok, status: resp.status, body: json }
// Treat explicit business-level failures as errors even when HTTP is 2xx.
// Many cloud functions return `{ ok: false, ... }` with HTTP 200.
const businessOk = (() => {
if (!json || typeof json !== 'object') return true
if (json.ok === false) return false
// uniCloud/uni-push demo often returns { errCode: 0|..., errMsg: '...' }
if (typeof json.errCode === 'number') return json.errCode === 0
if (typeof json.errCode === 'string' && json.errCode.trim() !== '') return json.errCode === '0'
return true
})()
return { ok: resp.ok && businessOk, httpOk: resp.ok, status: resp.status, body: json }
} catch (e) {
return { ok: false, error: String(e) }
}
@@ -239,7 +263,13 @@ async function start() {
if (CLOUD_FUNC_URL) {
const calls = await Promise.all(targets.map(cid => invokeCloudFuncForCid(CLOUD_FUNC_URL, PUSH_TOKEN, cid, notification && notification.title, notification && notification.body, payload)))
for (const c of calls) {
if (!c.ok) { allOk = false; lastNote = (c.error || JSON.stringify(c.body)).toString().substring(0, 1000); break }
if (!c.ok) {
allOk = false
const err = c.error ? String(c.error) : ''
const bodyStr = c.body ? JSON.stringify(c.body) : ''
lastNote = `cloudfunc failed: status=${c.status || ''} httpOk=${c.httpOk === true} ${err} ${bodyStr}`.trim().substring(0, 1000)
break
}
}
} else {
allOk = false