Files
medical-mall/server/push-server.js
2026-02-24 11:28:13 +08:00

136 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const fs = require('fs').promises
const path = require('path')
const fetch = require('node-fetch')
const PORT = process.env.PORT || 7301
const DATA_DIR = path.join(__dirname, 'data')
const DEVICES_FILE = path.join(DATA_DIR, 'push_devices.json')
async function ensureDataDir() {
try {
await fs.mkdir(DATA_DIR, { recursive: true })
try {
await fs.access(DEVICES_FILE)
} catch (e) {
await fs.writeFile(DEVICES_FILE, '[]', 'utf8')
}
} catch (e) {
console.error('无法创建数据目录:', e)
process.exit(1)
}
}
async function readDevices() {
try {
const txt = await fs.readFile(DEVICES_FILE, 'utf8')
return JSON.parse(txt || '[]')
} catch (e) {
return []
}
}
async function writeDevices(devices) {
await fs.writeFile(DEVICES_FILE, JSON.stringify(devices, null, 2), 'utf8')
}
async function start() {
await ensureDataDir()
const app = express()
app.use(cors())
app.use(bodyParser.json())
app.get('/health', (req, res) => res.json({ ok: true }))
// 注册或更新设备
app.post('/api/v1/push/register', async (req, res) => {
const { cid, user_id, platform } = req.body || {}
if (!cid) return res.status(400).json({ error: 'cid required' })
const devices = await readDevices()
let found = devices.find(d => d.cid === cid)
const now = new Date().toISOString()
if (found) {
found.user_id = user_id ?? found.user_id
found.platform = platform ?? found.platform
found.updated_at = now
found.active = true
} else {
found = { cid, user_id: user_id ?? null, platform: platform ?? null, created_at: now, updated_at: now, active: true }
devices.push(found)
}
await writeDevices(devices)
return res.json({ ok: true, cid })
})
// 注销设备(可选移除或置为 inactive
app.post('/api/v1/push/unregister', async (req, res) => {
const { cid, user_id } = req.body || {}
if (!cid && !user_id) return res.status(400).json({ error: 'cid or user_id required' })
let devices = await readDevices()
if (cid) {
devices = devices.map(d => d.cid === cid ? Object.assign({}, d, { active: false, updated_at: new Date().toISOString() }) : d)
} else if (user_id) {
devices = devices.map(d => d.user_id === user_id ? Object.assign({}, d, { active: false, updated_at: new Date().toISOString() }) : d)
}
await writeDevices(devices)
return res.json({ ok: true })
})
// 列出设备
app.get('/api/v1/push/devices', async (req, res) => {
const { user_id, active } = req.query
let devices = await readDevices()
if (user_id) devices = devices.filter(d => String(d.user_id) === String(user_id))
if (active != null) devices = devices.filter(d => String(!!d.active) === String(active === 'true'))
res.json({ ok: true, total: devices.length, data: devices })
})
// 发送推送mock 或代理到真实 provider当环境变量 PUSH_PROXY_URL 设置时会代理请求)
app.post('/api/v1/push/send', async (req, res) => {
const { cids, user_id, notification, payload } = req.body || {}
if ((!cids || cids.length === 0) && !user_id) return res.status(400).json({ error: 'cids or user_id required' })
let targets = []
if (cids && cids.length > 0) targets = cids
else if (user_id) {
const devices = await readDevices()
targets = devices.filter(d => String(d.user_id) === String(user_id) && d.active).map(d => d.cid)
}
// 如果配置了 PUSH_PROXY_URL 则转发到外部推送服务(例如 uni-push2 的服务端 API
const proxyUrl = process.env.PUSH_PROXY_URL || ''
const proxyToken = process.env.PUSH_PROXY_TOKEN || ''
if (proxyUrl) {
try {
const resp = await fetch(proxyUrl, {
method: 'POST',
headers: Object.assign({ 'Content-Type': 'application/json' }, proxyToken ? { Authorization: `Bearer ${proxyToken}` } : {}),
body: JSON.stringify({ targets, notification, payload })
})
const data = await resp.text()
return res.json({ ok: true, proxied: true, status: resp.status, response: data })
} catch (e) {
console.warn('代理推送失败:', e)
return res.status(500).json({ ok: false, error: String(e) })
}
}
// 否则仅记录并返回模拟结果
console.log('Mock push to', targets.length, 'clients')
console.log('notification:', notification)
console.log('payload:', payload)
return res.json({ ok: true, mocked: true, sent: targets.length })
})
app.listen(PORT, () => {
console.log(`Push server listening on http://localhost:${PORT}`)
})
}
start().catch(e => {
console.error('启动失败:', e)
process.exit(1)
})